diff --git a/Cargo.lock b/Cargo.lock index d9dc346..35f8c5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,7 @@ name = "dental-notation" version = "0.1.0" dependencies = [ "clap", + "owo-colors", ] [[package]] @@ -120,6 +121,12 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" +[[package]] +name = "owo-colors" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" + [[package]] name = "proc-macro-error" version = "1.0.4" diff --git a/Cargo.toml b/Cargo.toml index 4c51d97..b40ffe2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -clap = {version = "3.2.5", features = [ "derive" ] } \ No newline at end of file +clap = {version = "3.2.5", features = [ "derive" ] } +owo-colors = "3" \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs index 0b2ea81..85a99b2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,4 @@ +use crate::display::{ToothCliFormatter, ToothDisplay}; use crate::lib::NotationKind; use crate::lib::Tooth; use clap::{Parser, Subcommand, ValueEnum}; @@ -20,6 +21,14 @@ enum Action { #[clap(value_parser)] value: String, }, + Display { + #[clap(value_enum, short = 'n', long)] + notation: NotationKindArg, + #[clap(takes_value = false, short = 'p', long)] + primary: bool, + #[clap(value_parser)] + value: Option, + }, } #[derive(Parser, Debug)] struct Args { @@ -27,24 +36,58 @@ struct Args { action: Action, } -fn convert_kind(kind_arg: &NotationKindArg) -> NotationKind { - match kind_arg { - NotationKindArg::Iso => NotationKind::Iso, - NotationKindArg::Uns => NotationKind::Uns, - NotationKindArg::Alphanumeric => NotationKind::Alphanumeric, +impl Into for NotationKindArg { + fn into(self) -> NotationKind { + (&self).into() } } + +impl Into for &NotationKindArg { + fn into(self) -> NotationKind { + match self { + 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 tooth_result = Tooth::from(value, &from.into()); + let output_string = match tooth_result { + Ok(tooth) => tooth.to(&to.into()), Err(err) => err, - Ok(ok) => ok, }; + println!("{}", output_string); } + Action::Display { + notation, + primary, + value, + } => { + let converted_value = match value { + Some(t) => { + let tooth_result = Tooth::from(t, ¬ation.into()); + match tooth_result { + Ok(tr) => Some(tr), + Err(err) => { + eprintln!("{}", err); + return; + } + } + } + None => None, + }; + println!( + "{}", + ToothCliFormatter::format(¬ation.into(), !primary, &converted_value) + ); + } }; } diff --git a/src/display/cli.rs b/src/display/cli.rs new file mode 100644 index 0000000..a153aa5 --- /dev/null +++ b/src/display/cli.rs @@ -0,0 +1,110 @@ +use super::JawKind; +use crate::display::ToothDisplay; +use crate::lib::{NotationKind, QuadrantKind, Tooth, ToothType}; +use owo_colors::{OwoColorize, Style}; + +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) + } + + pub fn format_dentition( + 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 + } +} + +impl ToothDisplay for ToothCliFormatter { + fn format(notation: &NotationKind, permanent: bool, selected_value: &Option) -> String { + ToothCliFormatter::format_dentition(notation, permanent, selected_value) + } +} diff --git a/src/display/mod.rs b/src/display/mod.rs new file mode 100644 index 0000000..6977820 --- /dev/null +++ b/src/display/mod.rs @@ -0,0 +1,13 @@ +pub use crate::display::cli::*; +use crate::lib::NotationKind; +use crate::lib::Tooth; +mod cli; + +pub enum JawKind { + Top, + Bottom, +} + +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..8a8e48d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,14 @@ pub enum QuadrantKind { BottomRight, } +pub enum ToothType { + Incisor, + Canine, + Premolar, + Molar, +} + +#[derive(PartialEq)] pub struct Tooth { quadrant: QuadrantKind, number: u8, @@ -20,7 +28,7 @@ pub struct Tooth { } impl Tooth { - fn _new(number: u8, quadrant: QuadrantKind, permanent: bool) -> Result { + pub fn new(number: u8, quadrant: QuadrantKind, permanent: bool) -> Result { if let Err(err) = Tooth::check_tooth_number(number, permanent) { return Err(err); } @@ -48,7 +56,7 @@ impl Tooth { } } - fn quadrant_max(permanent: bool) -> u8 { + pub fn quadrant_max(permanent: bool) -> u8 { if permanent { 8 } else { @@ -155,7 +163,7 @@ impl Tooth { }; if self.permanent { - value.to_string() + format!("{:2}", value) } else { ((value + 64) as char).to_string() } @@ -230,21 +238,39 @@ impl Tooth { quadrant.to_owned() + &number } - pub fn convert(from: &NotationKind, to: &NotationKind, value: &str) -> Result { - let tooth_result = match from { + 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) => match to { - NotationKind::Iso => Ok(tooth.to_iso()), - NotationKind::Uns => Ok(tooth.to_uns()), - NotationKind::Alphanumeric => Ok(tooth.to_alphanumeric()), - }, + 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)] diff --git a/src/main.rs b/src/main.rs index 79286f4..5f12f15 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod cli; +mod display; mod lib; fn main() {