diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..175bbc7 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,15 @@ +kind: pipeline +type: docker +name: default + +steps: + - name: publish + image: rust:slim + commands: + - /usr/bin/env cargo publish + environment: + CARGO_REGISTRY_TOKEN: + from_secret: cargo_token +trigger: + event: + - tag diff --git a/Cargo.lock b/Cargo.lock index d9dc346..6c5a3a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,237 +2,15 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "clap" -version = "3.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53da17d37dba964b9b3ecb5c5a1f193a2762c700e6829201e645b9381c99dc7" -dependencies = [ - "atty", - "bitflags", - "clap_derive", - "clap_lex", - "indexmap", - "once_cell", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_derive" -version = "3.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11d40217d16aee8508cc8e5fde8b4ff24639758608e5374e731b53f85749fb9" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5538cd660450ebeb4234cfecf8f2284b844ffc4c50531e66d584ad5b91293613" -dependencies = [ - "os_str_bytes", -] - [[package]] name = "dental-notation" -version = "0.1.0" +version = "1.0.0" dependencies = [ - "clap", + "owo-colors", ] [[package]] -name = "hashbrown" -version = "0.12.1" +name = "owo-colors" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "indexmap" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6392766afd7964e2531940894cffe4bd8d7d17dbc3c1c4857040fd4b33bdb3" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "libc" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - -[[package]] -name = "once_cell" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" - -[[package]] -name = "os_str_bytes" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "1.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" - -[[package]] -name = "unicode-ident" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" diff --git a/Cargo.toml b/Cargo.toml index 4c51d97..0107796 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "dental-notation" -version = "0.1.0" +version = "1.0.0" edition = "2021" +description = "Library converting between different tooth numbering systems" +repository = "https://git.barthes.xyz/guilhem/dental-notation" +license = "GPL-3.0-or-later" +categories = ["parsing"] [dependencies] -clap = {version = "3.2.5", features = [ "derive" ] } \ No newline at end of file +owo-colors = "3" \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index 0b2ea81..0000000 --- a/src/cli.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::lib::NotationKind; -use crate::lib::Tooth; -use clap::{Parser, Subcommand, ValueEnum}; - -#[derive(Copy, Clone, Debug, ValueEnum)] -enum NotationKindArg { - Iso, - Uns, - Alphanumeric, -} -#[derive(Debug, Subcommand)] -enum Action { - Convert { - #[clap(value_enum, short = 'f', long)] - from: NotationKindArg, - - #[clap(value_enum, short = 't', long)] - to: NotationKindArg, - - #[clap(value_parser)] - value: String, - }, -} -#[derive(Parser, Debug)] -struct Args { - #[clap(subcommand)] - action: Action, -} - -fn convert_kind(kind_arg: &NotationKindArg) -> NotationKind { - match kind_arg { - NotationKindArg::Iso => NotationKind::Iso, - NotationKindArg::Uns => NotationKind::Uns, - NotationKindArg::Alphanumeric => NotationKind::Alphanumeric, - } -} -pub fn run_cli() { - let args = Args::parse(); - - match &args.action { - Action::Convert { from, to, value } => { - let tooth_result = Tooth::convert(&convert_kind(from), &convert_kind(&to), value); - let output_string = match tooth_result { - Err(err) => err, - Ok(ok) => ok, - }; - println!("{}", output_string); - } - }; -} diff --git a/src/display/cli.rs b/src/display/cli.rs new file mode 100644 index 0000000..eaf762f --- /dev/null +++ b/src/display/cli.rs @@ -0,0 +1,105 @@ +use super::JawKind; +use crate::display::ToothDisplay; +use crate::{NotationKind, QuadrantKind, Tooth, ToothType}; +use alloc::format; +use alloc::string::String; +use owo_colors::{OwoColorize, Style}; + +/// Formatter used to display a complete jaw in a CLI. +pub struct ToothCliFormatter; + +impl ToothCliFormatter { + fn format_tooth( + tooth: &Tooth, + notation: &NotationKind, + selected_value: &Option, + ) -> String { + let tooth_label = tooth.to(notation); + + let mut style = Style::new(); + style = match &tooth.get_type() { + ToothType::Canine => style.yellow(), + ToothType::Incisor => style.green(), + ToothType::Premolar => style.cyan(), + ToothType::Molar => style.blue(), + }; + + if let Some(t) = selected_value { + if t == tooth { + style = style.reversed(); + } + } + format!(" {}", tooth_label.style(style)) + } + + fn format_quadrant( + notation: &NotationKind, + + permanent: bool, + quadrant: QuadrantKind, + selected_value: &Option, + ) -> String { + let max = Tooth::quadrant_max(permanent); + let format_tooth = |i| { + let tooth = Tooth::new(i, quadrant, permanent).unwrap(); + ToothCliFormatter::format_tooth(&tooth, notation, selected_value) + }; + if quadrant == QuadrantKind::TopLeft || quadrant == QuadrantKind::BottomLeft { + (1..=max).rev().map(format_tooth).collect() + } else { + (1..=max).map(format_tooth).collect() + } + } + + fn format_jaw( + notation: &NotationKind, + permanent: bool, + jaw: JawKind, + selected_value: &Option, + ) -> String { + let (quadrant_1, quadrant_2) = match jaw { + JawKind::Top => (QuadrantKind::TopLeft, QuadrantKind::TopRight), + JawKind::Bottom => (QuadrantKind::BottomLeft, QuadrantKind::BottomRight), + }; + format!( + " {} |{}", + ToothCliFormatter::format_quadrant(notation, permanent, quadrant_1, selected_value), + ToothCliFormatter::format_quadrant(notation, permanent, quadrant_2, selected_value) + ) + } + + fn calculate_separator_len(notation: &NotationKind, permanent: bool) -> u8 { + let item_count = Tooth::quadrant_max(permanent); + let item_width = match (notation, permanent) { + (NotationKind::Iso, _) => 2, + (NotationKind::Uns, true) => 2, + (NotationKind::Uns, false) => 1, + (NotationKind::Alphanumeric, _) => 3, + }; + (item_width + 1) * (item_count * 2 + 1) + } +} + +impl ToothDisplay for ToothCliFormatter { + fn format(notation: &NotationKind, permanent: bool, selected_value: &Option) -> String { + let jaw_len = ToothCliFormatter::calculate_separator_len(notation, permanent); + let mut result = String::with_capacity((jaw_len * 4).into()); + result.push_str(&ToothCliFormatter::format_jaw( + notation, + permanent, + JawKind::Top, + selected_value, + )); + result.push_str("\nR "); + result.push_str(&"-".repeat(jaw_len.into())); + result.push_str(" L\n"); + result.push_str(&ToothCliFormatter::format_jaw( + notation, + permanent, + JawKind::Bottom, + selected_value, + )); + + result + } +} diff --git a/src/display/mod.rs b/src/display/mod.rs new file mode 100644 index 0000000..f88217d --- /dev/null +++ b/src/display/mod.rs @@ -0,0 +1,17 @@ +//! Formatters to display a complete jaw. + +pub use crate::display::cli::*; +use crate::NotationKind; +use crate::Tooth; +use alloc::string::String; +mod cli; + +enum JawKind { + Top, + Bottom, +} + +/// Trait needed to format a complete jaw +pub trait ToothDisplay { + fn format(notation: &NotationKind, permanent: bool, selected_value: &Option) -> String; +} diff --git a/src/lib.rs b/src/lib.rs index 330fce4..66cacab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,778 +1,29 @@ -#[derive(Copy, Clone)] -pub enum NotationKind { - Iso, - Uns, - Alphanumeric, -} +/*! +This crate provides seralizing and deserializing from different text representation of dental notations. You can find the different available notation in the enum [NotationKind]. -#[derive(PartialEq, Clone, Copy, Debug)] -pub enum QuadrantKind { - TopLeft, - TopRight, - BottomLeft, - BottomRight, -} +It primarily exposes one struct, [Tooth], which operates the different conversions. It also contains another module, [`display`], used to format tooth for different +# Usage -pub struct Tooth { - quadrant: QuadrantKind, - number: u8, - permanent: bool, -} +This crate is [on crates.io](https://crates.io/crates/dental_notation) and can be +used by adding `dental_notation` to your dependencies in your project's `Cargo.toml`. -impl Tooth { - fn _new(number: u8, quadrant: QuadrantKind, permanent: bool) -> Result { - if let Err(err) = Tooth::check_tooth_number(number, permanent) { - return Err(err); - } +```toml +[dependencies] +dental_notation = "1" +``` - Ok(Tooth { - number, - quadrant, - permanent, - }) - } +If you're using Rust 2015, then you'll also need to add it to your crate root: - fn check_tooth_number(number: u8, permanent: bool) -> Result<(), String> { - if permanent && (number < 1 || number > 8) { - Err(format!( - "Permanent tooth {} should be in range [1; 8]", - number - )) - } else if !permanent && (number < 1 || number > 5) { - Err(format!( - "Primary tooth {} should be in range [1; 5]", - number - )) - } else { - Ok(()) - } - } +```rust +extern crate dental_notation; +``` - fn quadrant_max(permanent: bool) -> u8 { - if permanent { - 8 - } else { - 5 - } - } +*/ - pub fn from_iso(value: &str) -> Result { - if value.len() != 2 { - return Err(format!("{} is not a valid ISO dental notation", value)); - } - let mut char_iter = value.chars(); - let (quadrant, permanent) = match char_iter.next().unwrap() { - '1' => (QuadrantKind::TopLeft, true), - '2' => (QuadrantKind::TopRight, true), - '3' => (QuadrantKind::BottomRight, true), - '4' => (QuadrantKind::BottomLeft, true), - '5' => (QuadrantKind::TopLeft, false), - '6' => (QuadrantKind::TopRight, false), - '7' => (QuadrantKind::BottomRight, false), - '8' => (QuadrantKind::BottomLeft, false), - x => return Err(format!("Quadrant value {} not included in range [1; 8]", x)), - }; +#![no_std] +extern crate alloc; - let tooth_string = char_iter.next().unwrap(); - let tooth_number_option = tooth_string.to_digit(10); - - if tooth_number_option.is_none() { - return Err(format!("{} is not a number", tooth_string)); - } - let number = tooth_number_option.unwrap() as u8; - if let Err(err) = Tooth::check_tooth_number(number, permanent) { - return Err(err); - } - Ok(Tooth { - quadrant, - permanent, - number, - }) - } - - pub fn to_iso(&self) -> String { - let quadrant_number: u8 = match (&self.quadrant, self.permanent) { - (QuadrantKind::TopLeft, true) => 1, - (QuadrantKind::TopRight, true) => 2, - (QuadrantKind::BottomLeft, true) => 4, - (QuadrantKind::BottomRight, true) => 3, - (QuadrantKind::TopLeft, false) => 5, - (QuadrantKind::TopRight, false) => 6, - (QuadrantKind::BottomLeft, false) => 8, - (QuadrantKind::BottomRight, false) => 7, - }; - quadrant_number.to_string() + &self.number.to_string() - } - - pub fn from_uns(value: &str) -> Result { - let number_value_option = value.parse::(); - let mut permanent = false; - let uns = if number_value_option.is_ok() { - permanent = true; - number_value_option.unwrap() - } else { - (value.chars().next().expect("No value available") as i32 - 64) as u8 - }; - - if permanent && (uns < 1 || uns > 32) { - return Err(format!( - "UNS permanent tooth has to be in range [1; 32] (currently {})", - uns - )); - } else if !permanent && (uns < 1 || uns > 20) { - return Err(format!("UNS primary tooth has to be in range [A; T]")); - } - let max = Tooth::quadrant_max(permanent); - let quadrant = match ((uns - 1) / max) + 1 { - 1 => QuadrantKind::TopLeft, - 2 => QuadrantKind::TopRight, - 3 => QuadrantKind::BottomRight, - 4 => QuadrantKind::BottomLeft, - _ => return Err(format!("UNS tooth value not in range")), - }; - let number = if quadrant == QuadrantKind::TopRight || quadrant == QuadrantKind::BottomLeft { - ((uns - 1) % max) + 1 - } else { - max - ((uns - 1) % max) - }; - if let Err(err) = Tooth::check_tooth_number(number, permanent) { - return Err(err); - } - Ok(Tooth { - quadrant, - number, - permanent, - }) - } - - pub fn to_uns(&self) -> String { - let max = Tooth::quadrant_max(self.permanent); - let value: u8 = match (&self.quadrant, self.number) { - (QuadrantKind::TopLeft, x) => max - x + 1, - (QuadrantKind::TopRight, x) => x + max, - (QuadrantKind::BottomRight, x) => (max - x + 1) + max * 2, - (QuadrantKind::BottomLeft, x) => x + max * 3, - }; - - if self.permanent { - value.to_string() - } else { - ((value + 64) as char).to_string() - } - } - - pub fn from_alphanumeric(value: &str) -> Result { - if value.len() != 3 { - return Err(format!( - "{} is not a valid alphanumeric dental notation", - value - )); - } - let quadrant = match &value[0..2] { - "UL" => QuadrantKind::TopLeft, - "UR" => QuadrantKind::TopRight, - "LR" => QuadrantKind::BottomRight, - "LL" => QuadrantKind::BottomLeft, - x => { - return Err(format!( - "Quadrant value {} not a valid value (accepted: ['UL', 'UR', 'LR', 'LL'])", - x - )) - } - }; - - let tooth_string = &value[2..3]; - - let (number, permanent) = match tooth_string { - "1" => (1, true), - "2" => (2, true), - "3" => (3, true), - "4" => (4, true), - "5" => (5, true), - "6" => (6, true), - "7" => (7, true), - "8" => (8, true), - "A" => (1, false), - "B" => (2, false), - "C" => (3, false), - "D" => (4, false), - "E" => (5, false), - x => { - return Err(format!( - "Number value {} not a valid value (accepted: [1; 8] and ['A'; 'E'])", - x - )) - } - }; - if let Err(err) = Tooth::check_tooth_number(number, permanent) { - return Err(err); - } - Ok(Tooth { - quadrant, - permanent, - number, - }) - } - - pub fn to_alphanumeric(&self) -> String { - let quadrant = match &self.quadrant { - QuadrantKind::TopLeft => "UL", - QuadrantKind::TopRight => "UR", - QuadrantKind::BottomRight => "LR", - QuadrantKind::BottomLeft => "LL", - }; - - let number = if self.permanent { - self.number.to_string() - } else { - ((self.number + 64) as char).to_string() - }; - quadrant.to_owned() + &number - } - - pub fn convert(from: &NotationKind, to: &NotationKind, value: &str) -> Result { - let tooth_result = match from { - NotationKind::Iso => Tooth::from_iso(value), - NotationKind::Uns => Tooth::from_uns(value), - NotationKind::Alphanumeric => Tooth::from_alphanumeric(value), - }; - match tooth_result { - Ok(tooth) => match to { - NotationKind::Iso => Ok(tooth.to_iso()), - NotationKind::Uns => Ok(tooth.to_uns()), - NotationKind::Alphanumeric => Ok(tooth.to_alphanumeric()), - }, - Err(err) => Err(err), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - macro_rules! to { - ($name:ident, $func:ident, $quadrant:expr,$number:expr, $permanent:expr, $value:expr) => { - #[test] - fn $name() { - let tooth = Tooth { - quadrant: $quadrant, - number: $number, - permanent: $permanent, - }; - assert_eq!(tooth.$func(), $value); - } - }; - } - - macro_rules! from { - ($name:ident, $func:ident, $value:expr, $quadrant:expr,$number:expr, $permanent:expr) => { - #[test] - fn $name() { - let tooth = Tooth::$func($value).unwrap(); - - assert_eq!(tooth.quadrant, $quadrant); - assert_eq!(tooth.number, $number); - assert_eq!(tooth.permanent, $permanent); - } - }; - } - - macro_rules! from_fail { - ($name:ident, $func:ident, $value:expr) => { - #[test] - fn $name() { - assert!(Tooth::$func($value).is_err()); - } - }; - } - - to!(to_iso_11, to_iso, QuadrantKind::TopLeft, 1, true, "11"); - to!(to_iso_18, to_iso, QuadrantKind::TopLeft, 8, true, "18"); - to!(to_iso_21, to_iso, QuadrantKind::TopRight, 1, true, "21"); - to!(to_iso_28, to_iso, QuadrantKind::TopRight, 8, true, "28"); - to!(to_iso_31, to_iso, QuadrantKind::BottomRight, 1, true, "31"); - to!(to_iso_38, to_iso, QuadrantKind::BottomRight, 8, true, "38"); - to!(to_iso_41, to_iso, QuadrantKind::BottomLeft, 1, true, "41"); - to!(to_iso_48, to_iso, QuadrantKind::BottomLeft, 8, true, "48"); - to!(to_iso_51, to_iso, QuadrantKind::TopLeft, 1, false, "51"); - to!(to_iso_55, to_iso, QuadrantKind::TopLeft, 5, false, "55"); - to!(to_iso_61, to_iso, QuadrantKind::TopRight, 1, false, "61"); - to!(to_iso_65, to_iso, QuadrantKind::TopRight, 5, false, "65"); - to!(to_iso_71, to_iso, QuadrantKind::BottomRight, 1, false, "71"); - to!(to_iso_75, to_iso, QuadrantKind::BottomRight, 5, false, "75"); - to!(to_iso_81, to_iso, QuadrantKind::BottomLeft, 1, false, "81"); - to!(to_iso_85, to_iso, QuadrantKind::BottomLeft, 5, false, "85"); - - from!(from_iso_11, from_iso, "11", QuadrantKind::TopLeft, 1, true); - from!(from_iso_18, from_iso, "18", QuadrantKind::TopLeft, 8, true); - from!(from_iso_21, from_iso, "21", QuadrantKind::TopRight, 1, true); - from!(from_iso_28, from_iso, "28", QuadrantKind::TopRight, 8, true); - from!( - from_iso_31, - from_iso, - "31", - QuadrantKind::BottomRight, - 1, - true - ); - from!( - from_iso_38, - from_iso, - "38", - QuadrantKind::BottomRight, - 8, - true - ); - from!( - from_iso_41, - from_iso, - "41", - QuadrantKind::BottomLeft, - 1, - true - ); - from!( - from_iso_48, - from_iso, - "48", - QuadrantKind::BottomLeft, - 8, - true - ); - from!(from_iso_51, from_iso, "51", QuadrantKind::TopLeft, 1, false); - from!(from_iso_55, from_iso, "55", QuadrantKind::TopLeft, 5, false); - from!( - from_iso_61, - from_iso, - "61", - QuadrantKind::TopRight, - 1, - false - ); - from!( - from_iso_65, - from_iso, - "65", - QuadrantKind::TopRight, - 5, - false - ); - from!( - from_iso_71, - from_iso, - "71", - QuadrantKind::BottomRight, - 1, - false - ); - from!( - from_iso_75, - from_iso, - "75", - QuadrantKind::BottomRight, - 5, - false - ); - from!( - from_iso_81, - from_iso, - "81", - QuadrantKind::BottomLeft, - 1, - false - ); - from!( - from_iso_85, - from_iso, - "85", - QuadrantKind::BottomLeft, - 5, - false - ); - - from_fail!(from_iso_fail_10, from_iso, "10"); - from_fail!(from_iso_fail_19, from_iso, "19"); - from_fail!(from_iso_fail_20, from_iso, "20"); - from_fail!(from_iso_fail_29, from_iso, "29"); - from_fail!(from_iso_fail_30, from_iso, "30"); - from_fail!(from_iso_fail_39, from_iso, "39"); - from_fail!(from_iso_fail_40, from_iso, "40"); - from_fail!(from_iso_fail_49, from_iso, "49"); - from_fail!(from_iso_fail_50, from_iso, "50"); - from_fail!(from_iso_fail_56, from_iso, "56"); - from_fail!(from_iso_fail_60, from_iso, "60"); - from_fail!(from_iso_fail_66, from_iso, "66"); - from_fail!(from_iso_fail_70, from_iso, "70"); - from_fail!(from_iso_fail_76, from_iso, "76"); - from_fail!(from_iso_fail_80, from_iso, "80"); - from_fail!(from_iso_fail_86, from_iso, "86"); - - to!(to_uns_1, to_uns, QuadrantKind::TopLeft, 1, true, "8"); - to!(to_uns_8, to_uns, QuadrantKind::TopLeft, 8, true, "1"); - to!(to_uns_9, to_uns, QuadrantKind::TopRight, 1, true, "9"); - to!(to_uns_16, to_uns, QuadrantKind::TopRight, 8, true, "16"); - to!(to_uns_17, to_uns, QuadrantKind::BottomRight, 1, true, "24"); - to!(to_uns_24, to_uns, QuadrantKind::BottomRight, 8, true, "17"); - to!(to_uns_25, to_uns, QuadrantKind::BottomLeft, 1, true, "25"); - to!(to_uns_32, to_uns, QuadrantKind::BottomLeft, 8, true, "32"); - to!(to_uns_a, to_uns, QuadrantKind::TopLeft, 1, false, "E"); - to!(to_uns_e, to_uns, QuadrantKind::TopLeft, 5, false, "A"); - to!(to_uns_f, to_uns, QuadrantKind::TopRight, 1, false, "F"); - to!(to_uns_j, to_uns, QuadrantKind::TopRight, 5, false, "J"); - to!(to_uns_k, to_uns, QuadrantKind::BottomRight, 1, false, "O"); - to!(to_uns_o, to_uns, QuadrantKind::BottomRight, 5, false, "K"); - to!(to_uns_p, to_uns, QuadrantKind::BottomLeft, 1, false, "P"); - to!(to_uns_t, to_uns, QuadrantKind::BottomLeft, 5, false, "T"); - - from!(from_uns_8, from_uns, "8", QuadrantKind::TopLeft, 1, true); - from!(from_uns_1, from_uns, "1", QuadrantKind::TopLeft, 8, true); - from!(from_uns_9, from_uns, "9", QuadrantKind::TopRight, 1, true); - from!(from_uns_16, from_uns, "16", QuadrantKind::TopRight, 8, true); - from!( - from_uns_24, - from_uns, - "24", - QuadrantKind::BottomRight, - 1, - true - ); - from!( - from_uns_17, - from_uns, - "17", - QuadrantKind::BottomRight, - 8, - true - ); - from!( - from_uns_25, - from_uns, - "25", - QuadrantKind::BottomLeft, - 1, - true - ); - from!( - from_uns_32, - from_uns, - "32", - QuadrantKind::BottomLeft, - 8, - true - ); - from!(from_uns_e, from_uns, "E", QuadrantKind::TopLeft, 1, false); - from!(from_uns_a, from_uns, "A", QuadrantKind::TopLeft, 5, false); - from!(from_uns_f, from_uns, "F", QuadrantKind::TopRight, 1, false); - from!(from_uns_j, from_uns, "J", QuadrantKind::TopRight, 5, false); - from!( - from_uns_o, - from_uns, - "O", - QuadrantKind::BottomRight, - 1, - false - ); - from!( - from_uns_k, - from_uns, - "K", - QuadrantKind::BottomRight, - 5, - false - ); - from!( - from_uns_p, - from_uns, - "P", - QuadrantKind::BottomLeft, - 1, - false - ); - from!( - from_uns_t, - from_uns, - "T", - QuadrantKind::BottomLeft, - 5, - false - ); - - from_fail!(from_uns_fail_0, from_uns, "0"); - from_fail!(from_uns_fail_33, from_uns, "33"); - from_fail!(from_uns_fail_at, from_uns, "@"); - from_fail!(from_uns_fail_u, from_uns, "U"); - - to!( - to_alphanumeric_ul1, - to_alphanumeric, - QuadrantKind::TopLeft, - 1, - true, - "UL1" - ); - to!( - to_alphanumeric_ul8, - to_alphanumeric, - QuadrantKind::TopLeft, - 8, - true, - "UL8" - ); - to!( - to_alphanumeric_ur1, - to_alphanumeric, - QuadrantKind::TopRight, - 1, - true, - "UR1" - ); - to!( - to_alphanumeric_ur8, - to_alphanumeric, - QuadrantKind::TopRight, - 8, - true, - "UR8" - ); - to!( - to_alphanumeric_lr1, - to_alphanumeric, - QuadrantKind::BottomRight, - 1, - true, - "LR1" - ); - to!( - to_alphanumeric_lr8, - to_alphanumeric, - QuadrantKind::BottomRight, - 8, - true, - "LR8" - ); - to!( - to_alphanumeric_ll1, - to_alphanumeric, - QuadrantKind::BottomLeft, - 1, - true, - "LL1" - ); - to!( - to_alphanumeric_ll8, - to_alphanumeric, - QuadrantKind::BottomLeft, - 8, - true, - "LL8" - ); - to!( - to_alphanumeric_ula, - to_alphanumeric, - QuadrantKind::TopLeft, - 1, - false, - "ULA" - ); - to!( - to_alphanumeric_ule, - to_alphanumeric, - QuadrantKind::TopLeft, - 5, - false, - "ULE" - ); - to!( - to_alphanumeric_ura, - to_alphanumeric, - QuadrantKind::TopRight, - 1, - false, - "URA" - ); - to!( - to_alphanumeric_ure, - to_alphanumeric, - QuadrantKind::TopRight, - 5, - false, - "URE" - ); - to!( - to_alphanumeric_lra, - to_alphanumeric, - QuadrantKind::BottomRight, - 1, - false, - "LRA" - ); - to!( - to_alphanumeric_lre, - to_alphanumeric, - QuadrantKind::BottomRight, - 5, - false, - "LRE" - ); - to!( - to_alphanumeric_lla, - to_alphanumeric, - QuadrantKind::BottomLeft, - 1, - false, - "LLA" - ); - to!( - to_alphanumeric_lle, - to_alphanumeric, - QuadrantKind::BottomLeft, - 5, - false, - "LLE" - ); - from!( - from_alphanumeric_ul1, - from_alphanumeric, - "UL1", - QuadrantKind::TopLeft, - 1, - true - ); - from!( - from_alphanumeric_ul8, - from_alphanumeric, - "UL8", - QuadrantKind::TopLeft, - 8, - true - ); - from!( - from_alphanumeric_ur1, - from_alphanumeric, - "UR1", - QuadrantKind::TopRight, - 1, - true - ); - from!( - from_alphanumeric_ur8, - from_alphanumeric, - "UR8", - QuadrantKind::TopRight, - 8, - true - ); - from!( - from_alphanumeric_lr1, - from_alphanumeric, - "LR1", - QuadrantKind::BottomRight, - 1, - true - ); - from!( - from_alphanumeric_lr8, - from_alphanumeric, - "LR8", - QuadrantKind::BottomRight, - 8, - true - ); - from!( - from_alphanumeric_ll1, - from_alphanumeric, - "LL1", - QuadrantKind::BottomLeft, - 1, - true - ); - from!( - from_alphanumeric_ll8, - from_alphanumeric, - "LL8", - QuadrantKind::BottomLeft, - 8, - true - ); - from!( - from_alphanumeric_ula, - from_alphanumeric, - "ULA", - QuadrantKind::TopLeft, - 1, - false - ); - from!( - from_alphanumeric_ule, - from_alphanumeric, - "ULE", - QuadrantKind::TopLeft, - 5, - false - ); - from!( - from_alphanumeric_ura, - from_alphanumeric, - "URA", - QuadrantKind::TopRight, - 1, - false - ); - from!( - from_alphanumeric_ure, - from_alphanumeric, - "URE", - QuadrantKind::TopRight, - 5, - false - ); - from!( - from_alphanumeric_lra, - from_alphanumeric, - "LRA", - QuadrantKind::BottomRight, - 1, - false - ); - from!( - from_alphanumeric_lre, - from_alphanumeric, - "LRE", - QuadrantKind::BottomRight, - 5, - false - ); - from!( - from_alphanumeric_lla, - from_alphanumeric, - "LLA", - QuadrantKind::BottomLeft, - 1, - false - ); - from!( - from_alphanumeric_lle, - from_alphanumeric, - "LLE", - QuadrantKind::BottomLeft, - 5, - false - ); - from_fail!(from_alphanumeric_fail_ul0, from_alphanumeric, "UL0"); - from_fail!(from_alphanumeric_fail_ul9, from_alphanumeric, "UL9"); - from_fail!(from_alphanumeric_fail_ur0, from_alphanumeric, "UR0"); - from_fail!(from_alphanumeric_fail_ur9, from_alphanumeric, "UR9"); - from_fail!(from_alphanumeric_fail_lr0, from_alphanumeric, "LR0"); - from_fail!(from_alphanumeric_fail_lr9, from_alphanumeric, "LR9"); - from_fail!(from_alphanumeric_fail_ll0, from_alphanumeric, "LL0"); - from_fail!(from_alphanumeric_fail_ll9, from_alphanumeric, "LL9"); - from_fail!(from_alphanumeric_fail_ulat, from_alphanumeric, "UL@"); - from_fail!(from_alphanumeric_fail_ulf, from_alphanumeric, "ULF"); - from_fail!(from_alphanumeric_fail_urat, from_alphanumeric, "UR@"); - from_fail!(from_alphanumeric_fail_urf, from_alphanumeric, "URF"); - from_fail!(from_alphanumeric_fail_lrat, from_alphanumeric, "LR@"); - from_fail!(from_alphanumeric_fail_lrf, from_alphanumeric, "LRF"); - from_fail!(from_alphanumeric_fail_llat, from_alphanumeric, "LL@"); - from_fail!(from_alphanumeric_fail_llf, from_alphanumeric, "LLF"); -} +pub mod display; +pub mod tooth; +#[doc(inline)] +pub use tooth::*; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 79286f4..0000000 --- a/src/main.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod cli; -mod lib; - -fn main() { - cli::run_cli() -} diff --git a/src/tooth.rs b/src/tooth.rs new file mode 100644 index 0000000..d4968c0 --- /dev/null +++ b/src/tooth.rs @@ -0,0 +1,815 @@ +use crate::alloc::borrow::ToOwned; +use alloc::format; +use alloc::string::{String, ToString}; + +#[derive(Copy, Clone)] +pub enum NotationKind { + /// [FDI / ISO 3950 notation](https://en.wikipedia.org/wiki/FDI_World_Dental_Federation_notation) + Iso, + /// [UNS (Universal Numbering System) notation](https://en.wikipedia.org/wiki/Universal_Numbering_System) + Uns, + /// [Alphanumeric notation](https://en.wikipedia.org/wiki/Dental_notation#Alphanumeric_notation) + Alphanumeric, +} + +#[derive(PartialEq, Clone, Copy, Debug)] +pub enum QuadrantKind { + TopLeft, + TopRight, + BottomLeft, + BottomRight, +} + +pub enum ToothType { + Incisor, + Canine, + Premolar, + Molar, +} + +/// Represent a tooth notation. It provides the different methods to convert from and to different notations in two different ways: +/// - [`from`](Tooth::from) and [`to`](Tooth::to), and providing the [NotationKind] as parameter, +/// - `from_xx` and `to_xx`, `xx` being one of the following: `iso`, `uns` and `alphanumeric. +#[derive(PartialEq)] +pub struct Tooth { + quadrant: QuadrantKind, + number: u8, + permanent: bool, +} + +impl Tooth { + pub fn new(number: u8, quadrant: QuadrantKind, permanent: bool) -> Result { + if let Err(err) = Tooth::check_tooth_number(number, permanent) { + return Err(err); + } + + Ok(Tooth { + number, + quadrant, + permanent, + }) + } + + fn check_tooth_number(number: u8, permanent: bool) -> Result<(), String> { + if permanent && (number < 1 || number > 8) { + Err(format!( + "Permanent tooth {} should be in range [1; 8]", + number + )) + } else if !permanent && (number < 1 || number > 5) { + Err(format!( + "Primary tooth {} should be in range [1; 5]", + number + )) + } else { + Ok(()) + } + } + + /// Get the maximum value for a tooth number (depending if it is a primary dentition or not) + pub fn quadrant_max(permanent: bool) -> u8 { + if permanent { + 8 + } else { + 5 + } + } + + pub fn from_iso(value: &str) -> Result { + if value.len() != 2 { + return Err(format!("{} is not a valid ISO dental notation", value)); + } + let mut char_iter = value.chars(); + let (quadrant, permanent) = match char_iter.next().unwrap() { + '1' => (QuadrantKind::TopLeft, true), + '2' => (QuadrantKind::TopRight, true), + '3' => (QuadrantKind::BottomRight, true), + '4' => (QuadrantKind::BottomLeft, true), + '5' => (QuadrantKind::TopLeft, false), + '6' => (QuadrantKind::TopRight, false), + '7' => (QuadrantKind::BottomRight, false), + '8' => (QuadrantKind::BottomLeft, false), + x => return Err(format!("Quadrant value {} not included in range [1; 8]", x)), + }; + + let tooth_string = char_iter.next().unwrap(); + let tooth_number_option = tooth_string.to_digit(10); + + if tooth_number_option.is_none() { + return Err(format!("{} is not a number", tooth_string)); + } + let number = tooth_number_option.unwrap() as u8; + if let Err(err) = Tooth::check_tooth_number(number, permanent) { + return Err(err); + } + Ok(Tooth { + quadrant, + permanent, + number, + }) + } + + pub fn to_iso(&self) -> String { + let quadrant_number: u8 = match (&self.quadrant, self.permanent) { + (QuadrantKind::TopLeft, true) => 1, + (QuadrantKind::TopRight, true) => 2, + (QuadrantKind::BottomLeft, true) => 4, + (QuadrantKind::BottomRight, true) => 3, + (QuadrantKind::TopLeft, false) => 5, + (QuadrantKind::TopRight, false) => 6, + (QuadrantKind::BottomLeft, false) => 8, + (QuadrantKind::BottomRight, false) => 7, + }; + quadrant_number.to_string() + &self.number.to_string() + } + + pub fn from_uns(value: &str) -> Result { + let number_value_option = value.parse::(); + let mut permanent = false; + let uns = if number_value_option.is_ok() { + permanent = true; + number_value_option.unwrap() + } else { + (value.chars().next().expect("No value available") as i32 - 64) as u8 + }; + + if permanent && (uns < 1 || uns > 32) { + return Err(format!( + "UNS permanent tooth has to be in range [1; 32] (currently {})", + uns + )); + } else if !permanent && (uns < 1 || uns > 20) { + return Err(format!("UNS primary tooth has to be in range [A; T]")); + } + let max = Tooth::quadrant_max(permanent); + let quadrant = match ((uns - 1) / max) + 1 { + 1 => QuadrantKind::TopLeft, + 2 => QuadrantKind::TopRight, + 3 => QuadrantKind::BottomRight, + 4 => QuadrantKind::BottomLeft, + _ => return Err(format!("UNS tooth value not in range")), + }; + let number = if quadrant == QuadrantKind::TopRight || quadrant == QuadrantKind::BottomLeft { + ((uns - 1) % max) + 1 + } else { + max - ((uns - 1) % max) + }; + if let Err(err) = Tooth::check_tooth_number(number, permanent) { + return Err(err); + } + Ok(Tooth { + quadrant, + number, + permanent, + }) + } + + pub fn to_uns(&self) -> String { + let max = Tooth::quadrant_max(self.permanent); + let value: u8 = match (&self.quadrant, self.number) { + (QuadrantKind::TopLeft, x) => max - x + 1, + (QuadrantKind::TopRight, x) => x + max, + (QuadrantKind::BottomRight, x) => (max - x + 1) + max * 2, + (QuadrantKind::BottomLeft, x) => x + max * 3, + }; + + if self.permanent { + format!("{:2}", value) + } else { + ((value + 64) as char).to_string() + } + } + + pub fn from_alphanumeric(value: &str) -> Result { + if value.len() != 3 { + return Err(format!( + "{} is not a valid alphanumeric dental notation", + value + )); + } + let quadrant = match &value[0..2] { + "UL" => QuadrantKind::TopLeft, + "UR" => QuadrantKind::TopRight, + "LR" => QuadrantKind::BottomRight, + "LL" => QuadrantKind::BottomLeft, + x => { + return Err(format!( + "Quadrant value {} not a valid value (accepted: ['UL', 'UR', 'LR', 'LL'])", + x + )) + } + }; + + let tooth_string = &value[2..3]; + + let (number, permanent) = match tooth_string { + "1" => (1, true), + "2" => (2, true), + "3" => (3, true), + "4" => (4, true), + "5" => (5, true), + "6" => (6, true), + "7" => (7, true), + "8" => (8, true), + "A" => (1, false), + "B" => (2, false), + "C" => (3, false), + "D" => (4, false), + "E" => (5, false), + x => { + return Err(format!( + "Number value {} not a valid value (accepted: [1; 8] and ['A'; 'E'])", + x + )) + } + }; + if let Err(err) = Tooth::check_tooth_number(number, permanent) { + return Err(err); + } + Ok(Tooth { + quadrant, + permanent, + number, + }) + } + + pub fn to_alphanumeric(&self) -> String { + let quadrant = match &self.quadrant { + QuadrantKind::TopLeft => "UL", + QuadrantKind::TopRight => "UR", + QuadrantKind::BottomRight => "LR", + QuadrantKind::BottomLeft => "LL", + }; + + let number = if self.permanent { + self.number.to_string() + } else { + ((self.number + 64) as char).to_string() + }; + quadrant.to_owned() + &number + } + + pub fn from(value: &str, from: &NotationKind) -> Result { + match from { + NotationKind::Iso => Tooth::from_iso(value), + NotationKind::Uns => Tooth::from_uns(value), + NotationKind::Alphanumeric => Tooth::from_alphanumeric(value), + } + } + pub fn to(&self, to: &NotationKind) -> String { + match to { + NotationKind::Iso => self.to_iso(), + NotationKind::Uns => self.to_uns(), + NotationKind::Alphanumeric => self.to_alphanumeric(), + } + } + + #[deprecated(since = "1.0.0", note = "Use 'to' and 'from' directly")] + pub fn convert(from: &NotationKind, to: &NotationKind, value: &str) -> Result { + let tooth_result = Tooth::from(value, from); + + match tooth_result { + Ok(tooth) => Ok(tooth.to(to)), + Err(err) => Err(err), + } + } + + pub fn get_type(&self) -> ToothType { + match (&self.number, &self.permanent) { + (1..=2, _) => ToothType::Incisor, + (3, _) => ToothType::Canine, + (4..=5, true) => ToothType::Premolar, + _ => ToothType::Molar, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + macro_rules! to { + ($name:ident, $func:ident, $quadrant:expr,$number:expr, $permanent:expr, $value:expr) => { + #[test] + fn $name() { + let tooth = Tooth { + quadrant: $quadrant, + number: $number, + permanent: $permanent, + }; + assert_eq!(tooth.$func(), $value); + } + }; + } + + macro_rules! from { + ($name:ident, $func:ident, $value:expr, $quadrant:expr,$number:expr, $permanent:expr) => { + #[test] + fn $name() { + let tooth = Tooth::$func($value).unwrap(); + + assert_eq!(tooth.quadrant, $quadrant); + assert_eq!(tooth.number, $number); + assert_eq!(tooth.permanent, $permanent); + } + }; + } + + macro_rules! from_fail { + ($name:ident, $func:ident, $value:expr) => { + #[test] + fn $name() { + assert!(Tooth::$func($value).is_err()); + } + }; + } + + to!(to_iso_11, to_iso, QuadrantKind::TopLeft, 1, true, "11"); + to!(to_iso_18, to_iso, QuadrantKind::TopLeft, 8, true, "18"); + to!(to_iso_21, to_iso, QuadrantKind::TopRight, 1, true, "21"); + to!(to_iso_28, to_iso, QuadrantKind::TopRight, 8, true, "28"); + to!(to_iso_31, to_iso, QuadrantKind::BottomRight, 1, true, "31"); + to!(to_iso_38, to_iso, QuadrantKind::BottomRight, 8, true, "38"); + to!(to_iso_41, to_iso, QuadrantKind::BottomLeft, 1, true, "41"); + to!(to_iso_48, to_iso, QuadrantKind::BottomLeft, 8, true, "48"); + to!(to_iso_51, to_iso, QuadrantKind::TopLeft, 1, false, "51"); + to!(to_iso_55, to_iso, QuadrantKind::TopLeft, 5, false, "55"); + to!(to_iso_61, to_iso, QuadrantKind::TopRight, 1, false, "61"); + to!(to_iso_65, to_iso, QuadrantKind::TopRight, 5, false, "65"); + to!(to_iso_71, to_iso, QuadrantKind::BottomRight, 1, false, "71"); + to!(to_iso_75, to_iso, QuadrantKind::BottomRight, 5, false, "75"); + to!(to_iso_81, to_iso, QuadrantKind::BottomLeft, 1, false, "81"); + to!(to_iso_85, to_iso, QuadrantKind::BottomLeft, 5, false, "85"); + + from!(from_iso_11, from_iso, "11", QuadrantKind::TopLeft, 1, true); + from!(from_iso_18, from_iso, "18", QuadrantKind::TopLeft, 8, true); + from!(from_iso_21, from_iso, "21", QuadrantKind::TopRight, 1, true); + from!(from_iso_28, from_iso, "28", QuadrantKind::TopRight, 8, true); + from!( + from_iso_31, + from_iso, + "31", + QuadrantKind::BottomRight, + 1, + true + ); + from!( + from_iso_38, + from_iso, + "38", + QuadrantKind::BottomRight, + 8, + true + ); + from!( + from_iso_41, + from_iso, + "41", + QuadrantKind::BottomLeft, + 1, + true + ); + from!( + from_iso_48, + from_iso, + "48", + QuadrantKind::BottomLeft, + 8, + true + ); + from!(from_iso_51, from_iso, "51", QuadrantKind::TopLeft, 1, false); + from!(from_iso_55, from_iso, "55", QuadrantKind::TopLeft, 5, false); + from!( + from_iso_61, + from_iso, + "61", + QuadrantKind::TopRight, + 1, + false + ); + from!( + from_iso_65, + from_iso, + "65", + QuadrantKind::TopRight, + 5, + false + ); + from!( + from_iso_71, + from_iso, + "71", + QuadrantKind::BottomRight, + 1, + false + ); + from!( + from_iso_75, + from_iso, + "75", + QuadrantKind::BottomRight, + 5, + false + ); + from!( + from_iso_81, + from_iso, + "81", + QuadrantKind::BottomLeft, + 1, + false + ); + from!( + from_iso_85, + from_iso, + "85", + QuadrantKind::BottomLeft, + 5, + false + ); + + from_fail!(from_iso_fail_10, from_iso, "10"); + from_fail!(from_iso_fail_19, from_iso, "19"); + from_fail!(from_iso_fail_20, from_iso, "20"); + from_fail!(from_iso_fail_29, from_iso, "29"); + from_fail!(from_iso_fail_30, from_iso, "30"); + from_fail!(from_iso_fail_39, from_iso, "39"); + from_fail!(from_iso_fail_40, from_iso, "40"); + from_fail!(from_iso_fail_49, from_iso, "49"); + from_fail!(from_iso_fail_50, from_iso, "50"); + from_fail!(from_iso_fail_56, from_iso, "56"); + from_fail!(from_iso_fail_60, from_iso, "60"); + from_fail!(from_iso_fail_66, from_iso, "66"); + from_fail!(from_iso_fail_70, from_iso, "70"); + from_fail!(from_iso_fail_76, from_iso, "76"); + from_fail!(from_iso_fail_80, from_iso, "80"); + from_fail!(from_iso_fail_86, from_iso, "86"); + + to!(to_uns_1, to_uns, QuadrantKind::TopLeft, 1, true, "8"); + to!(to_uns_8, to_uns, QuadrantKind::TopLeft, 8, true, "1"); + to!(to_uns_9, to_uns, QuadrantKind::TopRight, 1, true, "9"); + to!(to_uns_16, to_uns, QuadrantKind::TopRight, 8, true, "16"); + to!(to_uns_17, to_uns, QuadrantKind::BottomRight, 1, true, "24"); + to!(to_uns_24, to_uns, QuadrantKind::BottomRight, 8, true, "17"); + to!(to_uns_25, to_uns, QuadrantKind::BottomLeft, 1, true, "25"); + to!(to_uns_32, to_uns, QuadrantKind::BottomLeft, 8, true, "32"); + to!(to_uns_a, to_uns, QuadrantKind::TopLeft, 1, false, "E"); + to!(to_uns_e, to_uns, QuadrantKind::TopLeft, 5, false, "A"); + to!(to_uns_f, to_uns, QuadrantKind::TopRight, 1, false, "F"); + to!(to_uns_j, to_uns, QuadrantKind::TopRight, 5, false, "J"); + to!(to_uns_k, to_uns, QuadrantKind::BottomRight, 1, false, "O"); + to!(to_uns_o, to_uns, QuadrantKind::BottomRight, 5, false, "K"); + to!(to_uns_p, to_uns, QuadrantKind::BottomLeft, 1, false, "P"); + to!(to_uns_t, to_uns, QuadrantKind::BottomLeft, 5, false, "T"); + + from!(from_uns_8, from_uns, "8", QuadrantKind::TopLeft, 1, true); + from!(from_uns_1, from_uns, "1", QuadrantKind::TopLeft, 8, true); + from!(from_uns_9, from_uns, "9", QuadrantKind::TopRight, 1, true); + from!(from_uns_16, from_uns, "16", QuadrantKind::TopRight, 8, true); + from!( + from_uns_24, + from_uns, + "24", + QuadrantKind::BottomRight, + 1, + true + ); + from!( + from_uns_17, + from_uns, + "17", + QuadrantKind::BottomRight, + 8, + true + ); + from!( + from_uns_25, + from_uns, + "25", + QuadrantKind::BottomLeft, + 1, + true + ); + from!( + from_uns_32, + from_uns, + "32", + QuadrantKind::BottomLeft, + 8, + true + ); + from!(from_uns_e, from_uns, "E", QuadrantKind::TopLeft, 1, false); + from!(from_uns_a, from_uns, "A", QuadrantKind::TopLeft, 5, false); + from!(from_uns_f, from_uns, "F", QuadrantKind::TopRight, 1, false); + from!(from_uns_j, from_uns, "J", QuadrantKind::TopRight, 5, false); + from!( + from_uns_o, + from_uns, + "O", + QuadrantKind::BottomRight, + 1, + false + ); + from!( + from_uns_k, + from_uns, + "K", + QuadrantKind::BottomRight, + 5, + false + ); + from!( + from_uns_p, + from_uns, + "P", + QuadrantKind::BottomLeft, + 1, + false + ); + from!( + from_uns_t, + from_uns, + "T", + QuadrantKind::BottomLeft, + 5, + false + ); + + from_fail!(from_uns_fail_0, from_uns, "0"); + from_fail!(from_uns_fail_33, from_uns, "33"); + from_fail!(from_uns_fail_at, from_uns, "@"); + from_fail!(from_uns_fail_u, from_uns, "U"); + + to!( + to_alphanumeric_ul1, + to_alphanumeric, + QuadrantKind::TopLeft, + 1, + true, + "UL1" + ); + to!( + to_alphanumeric_ul8, + to_alphanumeric, + QuadrantKind::TopLeft, + 8, + true, + "UL8" + ); + to!( + to_alphanumeric_ur1, + to_alphanumeric, + QuadrantKind::TopRight, + 1, + true, + "UR1" + ); + to!( + to_alphanumeric_ur8, + to_alphanumeric, + QuadrantKind::TopRight, + 8, + true, + "UR8" + ); + to!( + to_alphanumeric_lr1, + to_alphanumeric, + QuadrantKind::BottomRight, + 1, + true, + "LR1" + ); + to!( + to_alphanumeric_lr8, + to_alphanumeric, + QuadrantKind::BottomRight, + 8, + true, + "LR8" + ); + to!( + to_alphanumeric_ll1, + to_alphanumeric, + QuadrantKind::BottomLeft, + 1, + true, + "LL1" + ); + to!( + to_alphanumeric_ll8, + to_alphanumeric, + QuadrantKind::BottomLeft, + 8, + true, + "LL8" + ); + to!( + to_alphanumeric_ula, + to_alphanumeric, + QuadrantKind::TopLeft, + 1, + false, + "ULA" + ); + to!( + to_alphanumeric_ule, + to_alphanumeric, + QuadrantKind::TopLeft, + 5, + false, + "ULE" + ); + to!( + to_alphanumeric_ura, + to_alphanumeric, + QuadrantKind::TopRight, + 1, + false, + "URA" + ); + to!( + to_alphanumeric_ure, + to_alphanumeric, + QuadrantKind::TopRight, + 5, + false, + "URE" + ); + to!( + to_alphanumeric_lra, + to_alphanumeric, + QuadrantKind::BottomRight, + 1, + false, + "LRA" + ); + to!( + to_alphanumeric_lre, + to_alphanumeric, + QuadrantKind::BottomRight, + 5, + false, + "LRE" + ); + to!( + to_alphanumeric_lla, + to_alphanumeric, + QuadrantKind::BottomLeft, + 1, + false, + "LLA" + ); + to!( + to_alphanumeric_lle, + to_alphanumeric, + QuadrantKind::BottomLeft, + 5, + false, + "LLE" + ); + from!( + from_alphanumeric_ul1, + from_alphanumeric, + "UL1", + QuadrantKind::TopLeft, + 1, + true + ); + from!( + from_alphanumeric_ul8, + from_alphanumeric, + "UL8", + QuadrantKind::TopLeft, + 8, + true + ); + from!( + from_alphanumeric_ur1, + from_alphanumeric, + "UR1", + QuadrantKind::TopRight, + 1, + true + ); + from!( + from_alphanumeric_ur8, + from_alphanumeric, + "UR8", + QuadrantKind::TopRight, + 8, + true + ); + from!( + from_alphanumeric_lr1, + from_alphanumeric, + "LR1", + QuadrantKind::BottomRight, + 1, + true + ); + from!( + from_alphanumeric_lr8, + from_alphanumeric, + "LR8", + QuadrantKind::BottomRight, + 8, + true + ); + from!( + from_alphanumeric_ll1, + from_alphanumeric, + "LL1", + QuadrantKind::BottomLeft, + 1, + true + ); + from!( + from_alphanumeric_ll8, + from_alphanumeric, + "LL8", + QuadrantKind::BottomLeft, + 8, + true + ); + from!( + from_alphanumeric_ula, + from_alphanumeric, + "ULA", + QuadrantKind::TopLeft, + 1, + false + ); + from!( + from_alphanumeric_ule, + from_alphanumeric, + "ULE", + QuadrantKind::TopLeft, + 5, + false + ); + from!( + from_alphanumeric_ura, + from_alphanumeric, + "URA", + QuadrantKind::TopRight, + 1, + false + ); + from!( + from_alphanumeric_ure, + from_alphanumeric, + "URE", + QuadrantKind::TopRight, + 5, + false + ); + from!( + from_alphanumeric_lra, + from_alphanumeric, + "LRA", + QuadrantKind::BottomRight, + 1, + false + ); + from!( + from_alphanumeric_lre, + from_alphanumeric, + "LRE", + QuadrantKind::BottomRight, + 5, + false + ); + from!( + from_alphanumeric_lla, + from_alphanumeric, + "LLA", + QuadrantKind::BottomLeft, + 1, + false + ); + from!( + from_alphanumeric_lle, + from_alphanumeric, + "LLE", + QuadrantKind::BottomLeft, + 5, + false + ); + from_fail!(from_alphanumeric_fail_ul0, from_alphanumeric, "UL0"); + from_fail!(from_alphanumeric_fail_ul9, from_alphanumeric, "UL9"); + from_fail!(from_alphanumeric_fail_ur0, from_alphanumeric, "UR0"); + from_fail!(from_alphanumeric_fail_ur9, from_alphanumeric, "UR9"); + from_fail!(from_alphanumeric_fail_lr0, from_alphanumeric, "LR0"); + from_fail!(from_alphanumeric_fail_lr9, from_alphanumeric, "LR9"); + from_fail!(from_alphanumeric_fail_ll0, from_alphanumeric, "LL0"); + from_fail!(from_alphanumeric_fail_ll9, from_alphanumeric, "LL9"); + from_fail!(from_alphanumeric_fail_ulat, from_alphanumeric, "UL@"); + from_fail!(from_alphanumeric_fail_ulf, from_alphanumeric, "ULF"); + from_fail!(from_alphanumeric_fail_urat, from_alphanumeric, "UR@"); + from_fail!(from_alphanumeric_fail_urf, from_alphanumeric, "URF"); + from_fail!(from_alphanumeric_fail_lrat, from_alphanumeric, "LR@"); + from_fail!(from_alphanumeric_fail_lrf, from_alphanumeric, "LRF"); + from_fail!(from_alphanumeric_fail_llat, from_alphanumeric, "LL@"); + from_fail!(from_alphanumeric_fail_llf, from_alphanumeric, "LLF"); +}