#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
pub mod combos;
pub mod dofinitions;
pub mod interaction;
pub mod keyboard;
mod macros;
pub mod magic;
pub mod prelude;
use combos::{Combos, ParseCombos};
use interaction::{KeyPos, Pos};
use keyboard::{ParseKeyboard, PhysicalKey, PhysicalKeyboard};
use magic::Magic;
use serde::{Deserialize, Serialize};
use serde_with::{DisplayFromStr, serde_as, skip_serializing_none};
use thiserror::Error;
use std::{collections::BTreeMap, num::ParseFloatError};
use dofinitions::*;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(from = "DofInternal", into = "DofInternal")]
pub struct Dof(DofInternal);
impl Dof {
pub fn name(&self) -> &str {
&self.0.name
}
pub fn authors(&self) -> &[String] {
&self.0.authors
}
pub const fn board(&self) -> &PhysicalKeyboard {
&self.0.board
}
pub fn form_factor(&self) -> FormFactor {
match &self.0.parsed_board {
ParseKeyboard::Named(n) => n.clone(),
_ => FormFactor::Custom("".into()),
}
}
pub const fn year(&self) -> Option<u32> {
self.0.year
}
pub fn description(&self) -> Option<&str> {
self.0.description.as_deref()
}
pub fn link(&self) -> Option<&str> {
self.0.link.as_deref()
}
pub fn languages(&self) -> &[Language] {
&self.0.languages
}
pub fn layers(&self) -> &BTreeMap<String, Layer> {
&self.0.layers
}
pub const fn anchor(&self) -> Anchor {
self.0.anchor
}
pub fn shape(&self) -> Shape {
self.fingering().shape()
}
pub const fn fingering(&self) -> &Fingering {
&self.0.fingering
}
pub fn fingering_name(&self) -> Option<&NamedFingering> {
self.0.fingering_name.as_ref()
}
pub fn main_layer(&self) -> &Layer {
self.0
.layers
.get("main")
.expect("Creating a Dof without a main layer should be impossible")
}
pub fn shift_layer(&self) -> &Layer {
self.0
.layers
.get("shift")
.expect("Creating a Dof without a shift layer should be impossible")
}
pub fn layer(&self, name: &str) -> Option<&Layer> {
self.0.layers.get(name)
}
pub fn magic(&self) -> &Magic {
&self.0.magic
}
pub fn combos(&self) -> &Combos {
&self.0.combos
}
pub fn keys(&self) -> Vec<DescriptiveKey<'_>> {
self.layers()
.iter()
.flat_map(|(name, layer)| {
layer
.rows()
.enumerate()
.zip(self.0.fingering.rows())
.zip(self.0.board.rows())
.flat_map(move |(((row, key_row), finger_row), phys_row)| {
key_row
.iter()
.enumerate()
.zip(finger_row)
.zip(phys_row)
.map(move |(((col, key), &finger), phys)| {
DescriptiveKey::new(key, name, row, col, finger, phys)
})
})
})
.collect()
}
pub fn into_inner(self) -> DofInternal {
self.0
}
}
impl From<DofInternal> for Dof {
fn from(inner: DofInternal) -> Self {
Dof(inner)
}
}
impl From<Dof> for DofInternal {
fn from(dof: Dof) -> Self {
dof.0
}
}
#[serde_as]
#[skip_serializing_none]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "DofIntermediate", into = "DofIntermediate")]
pub struct DofInternal {
pub name: String,
pub authors: Vec<String>,
pub board: PhysicalKeyboard,
pub parsed_board: ParseKeyboard,
pub year: Option<u32>,
pub description: Option<String>,
pub languages: Vec<Language>,
pub link: Option<String>,
pub layers: BTreeMap<String, Layer>,
pub anchor: Anchor,
pub magic: Magic,
pub combos: Combos,
pub fingering: Fingering,
pub fingering_name: Option<NamedFingering>,
pub has_generated_shift: bool,
}
impl TryFrom<DofIntermediate> for DofInternal {
type Error = DofError;
fn try_from(mut inter: DofIntermediate) -> std::result::Result<Self, Self::Error> {
let main_layer = inter.main_layer()?;
inter.validate_layer_keys(main_layer)?;
inter.validate_layer_shapes(main_layer)?;
let explicit_fingering = inter.explicit_fingering(main_layer)?;
let implicit_fingering = match inter.fingering.clone().unwrap_or_default() {
ParsedFingering::Implicit(f) => Some(f),
_ => None,
};
let has_generated_shift = if !inter.layers.contains_key("shift") {
inter.layers.insert(
"shift".into(),
DofIntermediate::generate_shift_layer(main_layer),
);
true
} else {
false
};
let anchor = match inter.anchor {
None => inter.board.anchor(),
Some(a) => a,
};
let board = PhysicalKeyboard::try_from(inter.board.clone())?
.resized(anchor, explicit_fingering.shape())?
.into_iter()
.map(|v| {
v.into_iter()
.map(PhysicalKey::normalized)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
.into();
let authors = inter.authors.unwrap_or_default();
let languages = match inter.languages {
Some(l) => l
.into_iter()
.map(|(lang, weight)| Language::new(&lang, weight))
.collect::<Vec<_>>(),
None => vec![Language::default()],
};
let combos = match inter.combos {
Some(combos) => combos.into_pos_layers(&inter.layers)?,
None => Default::default(),
};
let magic = inter.magic.unwrap_or_default();
Ok(DofInternal {
name: inter.name,
authors,
board,
parsed_board: inter.board,
year: inter.year,
description: inter.description,
languages,
link: inter.link,
layers: inter.layers,
anchor,
magic,
combos,
fingering: explicit_fingering,
fingering_name: implicit_fingering,
has_generated_shift,
})
}
}
impl From<DofInternal> for DofIntermediate {
fn from(mut dof: DofInternal) -> DofIntermediate {
if dof.has_generated_shift {
dof.layers.remove("shift");
}
let fingering = dof
.fingering_name
.map(ParsedFingering::Implicit)
.unwrap_or(ParsedFingering::Explicit(dof.fingering));
let fingering = if fingering == ParsedFingering::default() {
None
} else {
Some(fingering)
};
let authors = match dof.authors.len() {
0 => None,
_ => Some(dof.authors),
};
let languages = match dof.languages.as_slice() {
[lang] if lang == &Language::default() => None,
_ => Some(
dof.languages
.into_iter()
.map(|l| (l.language, l.weight))
.collect(),
),
};
let anchor = match &dof.parsed_board {
ParseKeyboard::Named(n) => match n.anchor() {
a if a == dof.anchor => None,
a => Some(a),
},
_ => None,
};
let magic = match dof.magic.is_empty() {
false => Some(dof.magic),
true => None,
};
let combos = dof.combos.into_parse_combos(&dof.layers);
DofIntermediate {
name: dof.name,
authors,
board: dof.parsed_board,
year: dof.year,
description: dof.description,
languages,
link: dof.link,
layers: dof.layers,
anchor,
magic,
combos,
fingering,
}
}
}
#[derive(Debug, Error, PartialEq)]
enum DofErrorInner {
#[error("This layout is missing a main layer")]
NoMainLayer,
#[error("Found these layer keys '{0:?}' however these layers do not actually exist")]
LayersNotFound(Vec<String>),
#[error("The shape of these layers: '{0:?}' are not the same as the main layer")]
IncompatibleLayerShapes(Vec<String>),
#[error("The layer shapes do not match the fingering shape")]
IncompatibleFingeringShape,
#[error("The provided layout + anchor don't fit within the given fingering")]
LayoutDoesntFit,
#[error("The anchor provided is bigger than the layout it is used for")]
AnchorBiggerThanLayout,
#[error("Combo key provided is empty. If this was intended, provide `~` instead.")]
EmptyComboKey,
#[error("Layer {0} containing combo {1} does not exist")]
UnknownComboLayer(String, String),
#[error("Combo {0} references key {1} at index {2}, which doesn't exist")]
InvalidKeyIndex(String, String, usize),
#[error("Couldn't parse Finger from '{0}'")]
FingerParseError(String),
#[error("Can't combine form factor '{0}' with fingering '{1}'")]
UnsupportedKeyboardFingeringCombo(FormFactor, NamedFingering),
#[error("Default fingering only exists for known keyboards: ansi, iso, ortho and colstag")]
FingeringForCustomKeyboard,
#[error("Couldn't parse physical key from '{0}' because a float couldn't be parsed")]
KeyParseError(String),
#[error("Couldn't parse physical key because the string is empty")]
EmptyPhysKey,
#[error("Expected 2, 3 or 4 values in physical key definition, found {0} for '{1}'")]
ValueAmountError(usize, String),
#[error("Form factor '{0}' does not match a default physical keyboard.")]
UnknownKeyboardType(FormFactor),
#[error("the provided layer name '{0}' is invalid")]
LayerDoesntExist(String),
#[error("the given position ({0}, {1}) is not available on the keyboard")]
InvalidPosition(u8, u8),
#[error("{0}")]
Infallible(#[from] std::convert::Infallible),
#[error("{0}")]
ParseFloatError(#[from] std::num::ParseFloatError),
#[error("{0}")]
ParseIntError(#[from] std::num::ParseIntError),
#[error("{0}")]
Custom(String),
}
use DofErrorInner as DErr;
type Result<T> = std::result::Result<T, DofError>;
#[derive(Debug, Error, PartialEq)]
#[error("{0}")]
pub struct DofError(#[source] Box<DofErrorInner>);
impl DofError {
pub fn custom(msg: &str) -> Self {
DofError(Box::new(DErr::Custom(msg.into())))
}
}
impl From<DofErrorInner> for DofError {
fn from(value: DofErrorInner) -> Self {
Self(Box::new(value))
}
}
impl From<ParseFloatError> for DofError {
fn from(value: ParseFloatError) -> Self {
DErr::ParseFloatError(value).into()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Language {
pub language: String,
pub weight: usize,
}
impl Default for Language {
fn default() -> Self {
Language {
language: "English".into(),
weight: 100,
}
}
}
impl Language {
pub fn new(language: &str, weight: usize) -> Self {
let language = language.into();
Self { language, weight }
}
pub fn only(language: &str) -> Self {
Self {
language: language.into(),
weight: 100,
}
}
}
pub trait Keyboard {
type K: Clone;
fn rows(&self) -> impl Iterator<Item = &Vec<Self::K>> {
self.inner().iter()
}
fn keys(&self) -> impl Iterator<Item = &Self::K> {
self.rows().flatten()
}
fn shape(&self) -> Shape {
self.rows().map(|r| r.len()).collect::<Vec<_>>().into()
}
fn row_count(&self) -> usize {
self.rows().count()
}
fn inner(&self) -> &[Vec<Self::K>];
fn into_inner(self) -> Vec<Vec<Self::K>>;
fn fits_in(&self, shape: &Shape) -> bool {
self.shape().fits_in(shape)
}
fn resized(&self, Anchor(x, y): Anchor, desired_shape: Shape) -> Result<Vec<Vec<Self::K>>> {
let (offset_x, offset_y) = (x as usize, y as usize);
let anchor_resized = self
.inner()
.get(offset_y..)
.ok_or(DErr::AnchorBiggerThanLayout)?
.iter()
.map(|r| r.get(offset_x..).ok_or(DErr::AnchorBiggerThanLayout.into()))
.collect::<Result<Vec<_>>>()?;
anchor_resized
.into_iter()
.zip(desired_shape.into_inner())
.map(|(row, shape_size)| {
row.get(..shape_size)
.ok_or(DErr::LayoutDoesntFit.into())
.map(|v| v.to_vec())
})
.collect::<Result<Vec<_>>>()
}
}
#[serde_as]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Fingering(#[serde_as(as = "Vec<FingeringStrAsRow>")] Vec<Vec<Finger>>);
impl Keyboard for Fingering {
type K = Finger;
fn inner(&self) -> &[Vec<Self::K>] {
&self.0
}
fn into_inner(self) -> Vec<Vec<Self::K>> {
self.0
}
}
impl From<Vec<Vec<Finger>>> for Fingering {
fn from(f: Vec<Vec<Finger>>) -> Self {
Self(f)
}
}
keyboard_conv!(Finger, FingeringStrAsRow);
#[serde_as]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ParsedFingering {
Explicit(Fingering),
Implicit(#[serde_as(as = "DisplayFromStr")] NamedFingering),
}
impl Default for ParsedFingering {
fn default() -> Self {
Self::Implicit(Default::default())
}
}
#[serde_as]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Layer(#[serde_as(as = "Vec<LayerStrAsRow>")] Vec<Vec<Key>>);
impl Keyboard for Layer {
type K = Key;
fn inner(&self) -> &[Vec<Self::K>] {
&self.0
}
fn into_inner(self) -> Vec<Vec<Self::K>> {
self.0
}
}
impl From<Vec<Vec<Key>>> for Layer {
fn from(f: Vec<Vec<Key>>) -> Self {
Self(f)
}
}
keyboard_conv!(Key, LayerStrAsRow);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Anchor(u8, u8);
impl Anchor {
pub const fn new(x: u8, y: u8) -> Self {
Anchor(x, y)
}
pub const fn x(&self) -> usize {
self.0 as usize
}
pub const fn y(&self) -> usize {
self.1 as usize
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DescriptiveKey<'a> {
output: &'a Key,
layer: &'a str,
pos: Pos,
finger: Finger,
phys: &'a PhysicalKey,
}
impl<'a> DescriptiveKey<'a> {
fn new(
output: &'a Key,
layer: &'a str,
row: usize,
col: usize,
finger: Finger,
physical_pos: &'a PhysicalKey,
) -> Self {
let pos = Pos::new(row, col);
Self {
output,
layer,
pos,
finger,
phys: physical_pos,
}
}
pub fn keypos(&self) -> KeyPos {
KeyPos::new(self.layer, self.pos)
}
pub const fn pos(&self) -> Pos {
self.pos
}
pub const fn row(&self) -> usize {
self.pos.row()
}
pub const fn col(&self) -> usize {
self.pos.col()
}
pub const fn finger(&self) -> Finger {
self.finger
}
pub const fn output(&self) -> &Key {
self.output
}
pub const fn physical_pos(&self) -> &PhysicalKey {
self.phys
}
pub fn layer_name(&self) -> &str {
self.layer
}
pub const fn is_on_finger(&self, finger: Finger) -> bool {
(self.finger as u8) == (finger as u8)
}
pub fn is_on_fingers(&self, fingers: &[Finger]) -> bool {
fingers.contains(&self.finger)
}
pub const fn is_on_left_hand(&self) -> bool {
self.finger.is_on_left_hand()
}
pub const fn is_on_right_hand(&self) -> bool {
self.finger.is_on_right_hand()
}
pub fn is_on_layer(&self, layer: &str) -> bool {
self.layer == layer
}
pub const fn is_char_key(&self) -> bool {
self.output.is_char()
}
pub const fn is_word_key(&self) -> bool {
self.output.is_word()
}
pub const fn is_empty_key(&self) -> bool {
self.output.is_empty()
}
pub const fn is_transparent_key(&self) -> bool {
self.output.is_transparent()
}
pub const fn is_layer_key(&self) -> bool {
self.output.is_layer()
}
pub const fn char_output(&self) -> Option<char> {
self.output.char_output()
}
pub fn word_output(&self) -> Option<&str> {
self.output.word_output()
}
pub fn layer_output(&self) -> Option<&str> {
self.output.layer_label()
}
pub fn magic_label(&self) -> Option<&str> {
self.output.magic_label()
}
}
#[allow(missing_docs)]
#[serde_as]
#[skip_serializing_none]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DofIntermediate {
pub name: String,
pub authors: Option<Vec<String>>,
pub board: ParseKeyboard,
pub year: Option<u32>,
pub description: Option<String>,
pub languages: Option<BTreeMap<String, usize>>,
pub link: Option<String>,
pub layers: BTreeMap<String, Layer>,
pub anchor: Option<Anchor>,
pub magic: Option<Magic>,
pub combos: Option<ParseCombos>,
pub fingering: Option<ParsedFingering>,
}
impl DofIntermediate {
pub fn main_layer(&self) -> Result<&Layer> {
self.layers.get("main").ok_or(DErr::NoMainLayer.into())
}
pub fn generate_shift_layer(main: &Layer) -> Layer {
main.0
.iter()
.map(|row| row.iter().map(|k| k.shifted()).collect::<Vec<_>>())
.collect::<Vec<_>>()
.into()
}
pub fn validate_layer_keys(&self, main: &Layer) -> Result<()> {
let layers_dont_exist = main
.keys()
.filter_map(|k| match k {
Key::Layer { label: n } if !self.layers.contains_key(n) => Some(n.clone()),
_ => None,
})
.collect::<Vec<_>>();
if layers_dont_exist.is_empty() {
Ok(())
} else {
Err(DErr::LayersNotFound(layers_dont_exist).into())
}
}
pub fn validate_layer_shapes(&self, main: &Layer) -> Result<()> {
let main_shape = main.shape();
let incompatible_shapes = self
.layers
.iter()
.map(|(name, l)| (name, l.shape()))
.filter(|(_, shape)| shape != &main_shape)
.map(|(name, _)| name.clone())
.collect::<Vec<_>>();
if incompatible_shapes.is_empty() {
Ok(())
} else {
Err(DErr::IncompatibleLayerShapes(incompatible_shapes).into())
}
}
pub fn explicit_fingering(&self, main: &Layer) -> Result<Fingering> {
use ParsedFingering::*;
let d = Default::default();
let fingering = match &self.fingering {
Some(f) => f,
None => &d,
};
match fingering {
Explicit(f) if f.shape() == main.shape() => Ok(f.clone()),
Explicit(_) => Err(DErr::IncompatibleFingeringShape.into()),
Implicit(named) => {
let fingering = self.board.fingering(named)?;
let anchor = match self.anchor {
Some(a) => a,
None => self.board.anchor(),
};
fingering.resized(anchor, main.shape()).map(Into::into)
}
}
}
}
#[cfg(test)]
mod tests {
use keyboard::{RelativeKey, RelativeKeyboard};
use crate::magic::MagicKey;
use super::*;
#[test]
fn no_main_layer() {
let minimal_test = DofIntermediate {
name: "Qwerty".into(),
authors: None,
board: ParseKeyboard::Named(FormFactor::Ansi),
year: None,
description: None,
languages: Default::default(),
link: None,
anchor: None,
layers: BTreeMap::new(),
fingering: Some(ParsedFingering::Implicit(NamedFingering::Angle)),
combos: None,
magic: None,
};
let v = DofInternal::try_from(minimal_test);
assert_eq!(v, Err(DofError::from(DErr::NoMainLayer)));
}
#[test]
fn parse_minimal() {
let minimal_json = include_str!("../example_dofs/minimal_parsable.dof");
let minimal_test = DofIntermediate {
name: "Qwerty".into(),
authors: None,
board: ParseKeyboard::Named(FormFactor::Ansi),
year: None,
description: None,
languages: None,
link: None,
anchor: None,
layers: BTreeMap::new(),
fingering: None,
combos: None,
magic: None,
};
let dof_minimal = serde_json::from_str::<DofIntermediate>(minimal_json)
.expect("couldn't parse implicit json");
assert_eq!(dof_minimal, minimal_test);
}
#[test]
fn minimal_succesful_dof() {
use Finger::*;
use Key::*;
let minimal_json = include_str!("../example_dofs/minimal_valid.dof");
let d = serde_json::from_str::<Dof>(minimal_json).expect("Couldn't serialize as Dof");
let d_manual = DofInternal {
name: "Qwerty".into(),
authors: Vec::new(),
board: PhysicalKeyboard::try_from(ParseKeyboard::Named(FormFactor::Ansi))
.unwrap()
.resized(FormFactor::Ansi.anchor(), vec![10, 11, 10].into())
.unwrap()
.into(),
parsed_board: ParseKeyboard::Named(FormFactor::Ansi),
year: None,
description: None,
languages: vec![Default::default()],
link: None,
anchor: Anchor::new(1, 1),
layers: BTreeMap::from_iter([
(
"main".into(),
vec![
vec![
Char('q'),
Char('w'),
Char('e'),
Char('r'),
Char('t'),
Char('y'),
Char('u'),
Char('i'),
Char('o'),
Char('p'),
],
vec![
Char('a'),
Char('s'),
Char('d'),
Char('f'),
Char('g'),
Char('h'),
Char('j'),
Char('k'),
Char('l'),
Char(';'),
Char('\''),
],
vec![
Char('z'),
Char('x'),
Char('c'),
Char('v'),
Char('b'),
Char('n'),
Char('m'),
Char(','),
Char('.'),
Char('/'),
],
]
.into(),
),
(
"shift".into(),
Layer(vec![
vec![
Char('Q'),
Char('W'),
Char('E'),
Char('R'),
Char('T'),
Char('Y'),
Char('U'),
Char('I'),
Char('O'),
Char('P'),
],
vec![
Char('A'),
Char('S'),
Char('D'),
Char('F'),
Char('G'),
Char('H'),
Char('J'),
Char('K'),
Char('L'),
Char(':'),
Char('\"'),
],
vec![
Char('Z'),
Char('X'),
Char('C'),
Char('V'),
Char('B'),
Char('N'),
Char('M'),
Char('<'),
Char('>'),
Char('?'),
],
]),
),
]),
combos: Default::default(),
magic: Default::default(),
fingering: {
vec![
vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP],
vec![LR, LM, LI, LI, LI, RI, RI, RM, RR, RP],
]
.into()
},
fingering_name: Some(NamedFingering::Angle),
has_generated_shift: true,
};
assert_eq!(d, Dof(d_manual));
let reconvert_json =
serde_json::to_string_pretty(&d).expect("Couldn't reconvert to json value");
println!("{reconvert_json}")
}
#[test]
fn parse_aptmak() {
use Finger::*;
use Key::*;
let aptmak_json = include_str!("../example_dofs/aptmak.dof");
let d = serde_json::from_str::<Dof>(aptmak_json).expect("Couldn't serialize as Dof");
let d_manual = DofInternal {
name: "Aptmak".into(),
authors: Vec::new(),
board: PhysicalKeyboard::try_from(ParseKeyboard::Named(FormFactor::Colstag))
.unwrap()
.resized(FormFactor::Colstag.anchor(), vec![10, 10, 10, 6].into())
.unwrap()
.into(),
parsed_board: ParseKeyboard::Named(FormFactor::Colstag),
year: None,
description: None,
languages: vec![Default::default()],
link: None,
anchor: FormFactor::Colstag.anchor(),
layers: BTreeMap::from_iter([
(
"main".into(),
vec![
vec![
Char('v'),
Char('w'),
Char('f'),
Char('p'),
Char('b'),
Char('j'),
Char('l'),
Char('u'),
Char('y'),
Char('\''),
],
vec![
Char('r'),
Char('s'),
Char('t'),
Char('h'),
Char('k'),
Char('x'),
Char('n'),
Char('a'),
Char('i'),
Char('o'),
],
vec![
Char(';'),
Char('c'),
Char('g'),
Char('d'),
Char('q'),
Char('z'),
Char('m'),
Char(','),
Char('.'),
Char('/'),
],
vec![
Empty,
Special(SpecialKey::Space),
Empty,
Empty,
Char('e'),
Empty,
],
]
.into(),
),
(
"shift".into(),
vec![
vec![
Char('V'),
Char('W'),
Char('F'),
Char('P'),
Char('B'),
Char('J'),
Char('L'),
Char('U'),
Char('Y'),
Char('"'),
],
vec![
Char('R'),
Char('S'),
Char('T'),
Char('H'),
Char('K'),
Char('X'),
Char('N'),
Char('A'),
Char('I'),
Char('O'),
],
vec![
Char(':'),
Char('C'),
Char('G'),
Char('D'),
Char('Q'),
Char('Z'),
Char('M'),
Char('<'),
Char('>'),
Char('?'),
],
vec![Empty, Transparent, Empty, Empty, Char('E'), Empty],
]
.into(),
),
]),
combos: Default::default(),
magic: Default::default(),
fingering: {
vec![
vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
vec![LT, LT, LT, RT, RT, RT],
]
.into()
},
fingering_name: Some(NamedFingering::Traditional),
has_generated_shift: true,
};
assert_eq!(d, Dof(d_manual));
let reconvert_json =
serde_json::to_string_pretty(&d).expect("Couldn't reconvert to json value");
println!("{reconvert_json}")
}
#[test]
fn maximal_succesful() {
let maximal_json = include_str!("../example_dofs/maximal.dof");
serde_json::from_str::<DofInternal>(maximal_json).expect("Couldn't parse or validate Dof");
}
#[test]
fn deserialize_minimal() {
let minimal_test = DofIntermediate {
name: "Qwerty".into(),
authors: None,
board: ParseKeyboard::Named(FormFactor::Ansi),
year: None,
description: None,
languages: None,
link: None,
anchor: None,
layers: BTreeMap::new(),
fingering: Some(ParsedFingering::Implicit(NamedFingering::Angle)),
combos: None,
magic: None,
};
let s = serde_json::to_string_pretty(&minimal_test).unwrap();
println!("{s}")
}
#[test]
fn buggy() {
let buggy_json = include_str!("../example_dofs/buggy.dof");
let buggy = serde_json::from_str::<Dof>(buggy_json).expect("couldn't parse buggy json");
assert_eq!(buggy.layers().len(), 4);
assert_eq!(buggy.anchor(), Anchor(0, 0));
}
fn rk(width: f64) -> RelativeKey {
RelativeKey {
width,
has_key: true,
}
}
#[test]
fn parse_maximal() {
use Finger::*;
use Key::*;
use SpecialKey::*;
use combos::ck;
let maximal_json = include_str!("../example_dofs/maximal.dof");
let maximal_test = DofIntermediate {
name: "Qwerty".into(),
authors: Some(vec!["Christopher Latham Sholes".into()]),
year: Some(1878),
description: Some("the OG. Without Qwerty, none of this would be necessary.".into()),
languages: Some(BTreeMap::from_iter([("english".to_owned(), 100)])),
link: Some("https://en.wikipedia.org/wiki/QWERTY".into()),
anchor: Some(Anchor::new(0, 0)),
layers: BTreeMap::from_iter([
(
"main".into(),
crate::Layer::from(vec![
vec![
Char('`'),
Char('1'),
Char('2'),
Char('3'),
Char('4'),
Char('5'),
Char('6'),
Char('7'),
Char('8'),
Char('9'),
Char('0'),
Char('-'),
Char('='),
Special(Backspace),
],
vec![
Special(Tab),
Char('q'),
Char('w'),
Char('e'),
Char('r'),
Char('t'),
Char('y'),
Char('u'),
Char('i'),
Char('o'),
Char('p'),
Char('['),
Char(']'),
Char('\\'),
],
vec![
Special(Caps),
Char('a'),
Char('s'),
Char('d'),
Char('f'),
Char('g'),
Char('h'),
Char('j'),
Char('k'),
Char('l'),
Char(';'),
Char('\''),
Special(Enter),
],
vec![
Special(Shift),
Char('z'),
Char('x'),
Char('c'),
Char('v'),
Char('b'),
Char('n'),
Char('m'),
Char(','),
Char('.'),
Char('/'),
Special(Shift),
],
vec![
Empty,
Empty,
Magic {
label: "mgc".into(),
},
Special(Space),
Layer {
label: "altgr".into(),
},
Empty,
Empty,
Char('ß'),
],
]),
),
(
"shift".into(),
crate::Layer::from(vec![
vec![
Char('~'),
Char('!'),
Char('@'),
Char('#'),
Char('$'),
Char('%'),
Char('^'),
Char('&'),
Char('*'),
Char('('),
Char(')'),
Char('_'),
Char('+'),
Special(Backspace),
],
vec![
Special(Tab),
Char('Q'),
Char('W'),
Char('E'),
Char('R'),
Char('T'),
Char('Y'),
Char('U'),
Char('I'),
Char('O'),
Char('P'),
Char('{'),
Char('}'),
Char('|'),
],
vec![
Special(Caps),
Char('A'),
Char('S'),
Char('D'),
Char('F'),
Char('G'),
Char('H'),
Char('J'),
Char('K'),
Char('L'),
Char(':'),
Char('\"'),
Special(Enter),
],
vec![
Special(Shift),
Char('Z'),
Char('X'),
Char('C'),
Char('V'),
Char('B'),
Char('N'),
Char('M'),
Char('<'),
Char('>'),
Char('?'),
Special(Shift),
],
vec![
Empty,
Empty,
Magic {
label: "mgc".into(),
},
Special(Space),
Word("altgr".into()),
Empty,
Empty,
Word("SS".into()),
],
]),
),
(
"altgr".into(),
crate::Layer::from(vec![
vec![
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Special(Backspace),
],
vec![
Special(Tab),
Transparent,
Transparent,
Transparent,
Transparent,
Word("tb".into()),
Transparent,
Char('ü'),
Transparent,
Char('ö'),
Transparent,
Transparent,
Transparent,
Transparent,
],
vec![
Special(Caps),
Char('ä'),
Transparent,
Transparent,
Transparent,
Magic {
label: "mgc2".into(),
},
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Special(Enter),
],
vec![
Special(Shift),
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Transparent,
Special(Shift),
],
vec![
Empty,
Empty,
Magic {
label: "mgc".into(),
},
Special(Space),
Transparent,
Empty,
Empty,
Empty,
],
]),
),
]),
fingering: {
Some(ParsedFingering::Explicit(Fingering::from(vec![
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP],
vec![LP, LR, LM, LI, LI, LI, RI, RI, RM, RR, RP, RP],
vec![LP, LP, LT, LT, LT, RT, RT, RP],
])))
},
board: ParseKeyboard::Relative(RelativeKeyboard::from(vec![
vec![
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(2.0),
],
vec![
rk(1.5),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.5),
],
vec![
rk(1.75),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(2.25),
],
vec![
rk(2.25),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(1.0),
rk(2.75),
],
vec![
rk(1.25),
rk(1.25),
rk(1.25),
rk(6.25),
rk(1.25),
rk(1.25),
rk(1.25),
rk(1.25),
],
])),
combos: Some(ParseCombos(BTreeMap::from([
(
"main".to_string(),
BTreeMap::from([
(vec![ck(Char('d'), 0), ck(Char('f'), 0)], Char('x')),
(vec![ck(Char('j'), 0), ck(Char('k'), 0)], Char('6')),
]),
),
(
"shift".to_string(),
BTreeMap::from([(vec![ck(Special(Shift), 1), ck(Char('?'), 0)], Char('X'))]),
),
]))),
magic: Some(crate::magic::Magic {
keys: BTreeMap::from([
(
"mgc".into(),
MagicKey {
label: "mgc".into(),
rules: BTreeMap::from([
("a".into(), "b".into()),
("abc".into(), "defghijklmnopqrstuvwxyz".into()),
]),
max_leading_length: 3,
max_output_length: 23,
},
),
(
"mgc2".into(),
MagicKey {
label: "mgc2".into(),
rules: BTreeMap::from([("more".into(), " magic".into())]),
max_leading_length: 4,
max_output_length: 6,
},
),
]),
}),
};
let dof_maximal = serde_json::from_str::<DofIntermediate>(maximal_json)
.expect("couldn't parse explicit json");
assert_eq!(dof_maximal, maximal_test);
}
#[test]
fn lang_fn() {
let languages = &[Language::new("English", 100)];
let languages = match languages {
[lang] if lang == &Language::default() => None,
_ => Some(languages),
};
println!("{:?}", languages)
}
}