From 970b7f6f64b941280361cfb196e6e56f0c023b20 Mon Sep 17 00:00:00 2001 From: Guilhem Date: Wed, 3 Aug 2022 15:54:52 +0200 Subject: [PATCH 1/4] feat: Separate convert into from and to --- src/cli.rs | 17 +++++++++++++++-- src/lib.rs | 25 +++++++++++++++++-------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 0b2ea81..c2ba11f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,4 @@ +use crate::display::format_dentition; use crate::lib::NotationKind; use crate::lib::Tooth; use clap::{Parser, Subcommand, ValueEnum}; @@ -20,6 +21,12 @@ 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, + }, } #[derive(Parser, Debug)] struct Args { @@ -34,17 +41,23 @@ fn convert_kind(kind_arg: &NotationKindArg) -> NotationKind { 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, &convert_kind(from)); + let output_string = match tooth_result { + Ok(tooth) => tooth.to(&convert_kind(to)), Err(err) => err, - Ok(ok) => ok, }; + println!("{}", output_string); } + Action::Display { notation, primary } => { + println!("{}", format_dentition(!primary, &convert_kind(notation))) + } }; } diff --git a/src/lib.rs b/src/lib.rs index 330fce4..801d45b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -230,18 +230,27 @@ 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), } } From 962c9db6f9d4960c686b3e077a6bf4860ff56104 Mon Sep 17 00:00:00 2001 From: Guilhem Date: Wed, 3 Aug 2022 15:57:29 +0200 Subject: [PATCH 2/4] feat: Add tooth display --- Cargo.lock | 7 ++++++ Cargo.toml | 3 ++- src/display.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 22 ++++++++++++++--- src/main.rs | 1 + 5 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 src/display.rs 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/display.rs b/src/display.rs new file mode 100644 index 0000000..9ade6bd --- /dev/null +++ b/src/display.rs @@ -0,0 +1,67 @@ +use crate::lib::NotationKind; +use crate::lib::{QuadrantKind, Tooth, ToothType}; +use owo_colors::{OwoColorize, Style}; + +enum JawKind { + Top, + Bottom, +} + +fn format_tooth(tooth: &Tooth, notation: &NotationKind) -> 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(), + }; + format!(" {}", tooth_label.style(style)) +} + +fn format_quadrant(quadrant: QuadrantKind, permanent: bool, notation: &NotationKind) -> String { + let max = Tooth::quadrant_max(permanent); + let format_tooth = |i| { + let tooth = Tooth::new(i, quadrant, permanent).unwrap(); + format_tooth(&tooth, notation) + }; + if quadrant == QuadrantKind::TopLeft || quadrant == QuadrantKind::BottomLeft { + (1..=max).rev().map(format_tooth).collect() + } else { + (1..=max).map(format_tooth).collect() + } +} + +fn format_jaw(jaw: JawKind, permanent: bool, notation: &NotationKind) -> String { + let (quadrant_1, quadrant_2) = match jaw { + JawKind::Top => (QuadrantKind::TopLeft, QuadrantKind::TopRight), + JawKind::Bottom => (QuadrantKind::BottomLeft, QuadrantKind::BottomRight), + }; + format!( + " {} |{}", + format_quadrant(quadrant_1, permanent, notation), + format_quadrant(quadrant_2, permanent, notation) + ) +} + +pub fn format_dentition(permanent: bool, notation: &NotationKind) -> String { + 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, + }; + let jaw_len = (item_width + 1) * (item_count * 2 + 1); + let mut result = String::with_capacity((jaw_len * 4).into()); + result.push_str(&format_jaw(JawKind::Top, permanent, notation)); + result.push_str("\nR "); + for _ in 1..=jaw_len { + result.push_str("-"); + } + result.push_str(" L\n"); + result.push_str(&format_jaw(JawKind::Bottom, permanent, notation)); + + result +} diff --git a/src/lib.rs b/src/lib.rs index 801d45b..2dab6a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,13 @@ pub enum QuadrantKind { BottomRight, } +pub enum ToothType { + Incisor, + Canine, + Premolar, + Molar, +} + pub struct Tooth { quadrant: QuadrantKind, number: u8, @@ -20,7 +27,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 +55,7 @@ impl Tooth { } } - fn quadrant_max(permanent: bool) -> u8 { + pub fn quadrant_max(permanent: bool) -> u8 { if permanent { 8 } else { @@ -155,7 +162,7 @@ impl Tooth { }; if self.permanent { - value.to_string() + format!("{:2}", value) } else { ((value + 64) as char).to_string() } @@ -254,6 +261,15 @@ impl Tooth { 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() { From 519ae2a261ac283df13ff9859ea97e39de3d3c74 Mon Sep 17 00:00:00 2001 From: Guilhem Date: Wed, 3 Aug 2022 16:29:59 +0200 Subject: [PATCH 3/4] refactor: Replace convert_kind to into --- src/cli.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index c2ba11f..3782ab2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -34,11 +34,19 @@ struct Args { action: Action, } -fn convert_kind(kind_arg: &NotationKindArg) -> NotationKind { - match kind_arg { +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, + } } } @@ -47,10 +55,10 @@ pub fn run_cli() { match &args.action { Action::Convert { from, to, value } => { - let tooth_result = Tooth::from(value, &convert_kind(from)); + let tooth_result = Tooth::from(value, &from.into()); let output_string = match tooth_result { - Ok(tooth) => tooth.to(&convert_kind(to)), + Ok(tooth) => tooth.to(&to.into()), Err(err) => err, }; From 1bae864faa5afebd955b9ea03e711f7f1d2659bc Mon Sep 17 00:00:00 2001 From: Guilhem Date: Thu, 4 Aug 2022 16:45:53 +0200 Subject: [PATCH 4/4] refactor: Create ToothDisplay trait --- src/cli.rs | 34 +++++++++++--- src/display.rs | 67 --------------------------- src/display/cli.rs | 110 +++++++++++++++++++++++++++++++++++++++++++++ src/display/mod.rs | 13 ++++++ src/lib.rs | 1 + 5 files changed, 152 insertions(+), 73 deletions(-) delete mode 100644 src/display.rs create mode 100644 src/display/cli.rs create mode 100644 src/display/mod.rs diff --git a/src/cli.rs b/src/cli.rs index 3782ab2..85a99b2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,4 @@ -use crate::display::format_dentition; +use crate::display::{ToothCliFormatter, ToothDisplay}; use crate::lib::NotationKind; use crate::lib::Tooth; use clap::{Parser, Subcommand, ValueEnum}; @@ -26,6 +26,8 @@ enum Action { notation: NotationKindArg, #[clap(takes_value = false, short = 'p', long)] primary: bool, + #[clap(value_parser)] + value: Option, }, } #[derive(Parser, Debug)] @@ -43,9 +45,9 @@ impl Into for NotationKindArg { impl Into for &NotationKindArg { fn into(self) -> NotationKind { match self { - NotationKindArg::Iso => NotationKind::Iso, - NotationKindArg::Uns => NotationKind::Uns, - NotationKindArg::Alphanumeric => NotationKind::Alphanumeric, + NotationKindArg::Iso => NotationKind::Iso, + NotationKindArg::Uns => NotationKind::Uns, + NotationKindArg::Alphanumeric => NotationKind::Alphanumeric, } } } @@ -64,8 +66,28 @@ pub fn run_cli() { println!("{}", output_string); } - Action::Display { notation, primary } => { - println!("{}", format_dentition(!primary, &convert_kind(notation))) + 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.rs b/src/display.rs deleted file mode 100644 index 9ade6bd..0000000 --- a/src/display.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::lib::NotationKind; -use crate::lib::{QuadrantKind, Tooth, ToothType}; -use owo_colors::{OwoColorize, Style}; - -enum JawKind { - Top, - Bottom, -} - -fn format_tooth(tooth: &Tooth, notation: &NotationKind) -> 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(), - }; - format!(" {}", tooth_label.style(style)) -} - -fn format_quadrant(quadrant: QuadrantKind, permanent: bool, notation: &NotationKind) -> String { - let max = Tooth::quadrant_max(permanent); - let format_tooth = |i| { - let tooth = Tooth::new(i, quadrant, permanent).unwrap(); - format_tooth(&tooth, notation) - }; - if quadrant == QuadrantKind::TopLeft || quadrant == QuadrantKind::BottomLeft { - (1..=max).rev().map(format_tooth).collect() - } else { - (1..=max).map(format_tooth).collect() - } -} - -fn format_jaw(jaw: JawKind, permanent: bool, notation: &NotationKind) -> String { - let (quadrant_1, quadrant_2) = match jaw { - JawKind::Top => (QuadrantKind::TopLeft, QuadrantKind::TopRight), - JawKind::Bottom => (QuadrantKind::BottomLeft, QuadrantKind::BottomRight), - }; - format!( - " {} |{}", - format_quadrant(quadrant_1, permanent, notation), - format_quadrant(quadrant_2, permanent, notation) - ) -} - -pub fn format_dentition(permanent: bool, notation: &NotationKind) -> String { - 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, - }; - let jaw_len = (item_width + 1) * (item_count * 2 + 1); - let mut result = String::with_capacity((jaw_len * 4).into()); - result.push_str(&format_jaw(JawKind::Top, permanent, notation)); - result.push_str("\nR "); - for _ in 1..=jaw_len { - result.push_str("-"); - } - result.push_str(" L\n"); - result.push_str(&format_jaw(JawKind::Bottom, permanent, notation)); - - result -} 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 2dab6a9..8a8e48d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ pub enum ToothType { Molar, } +#[derive(PartialEq)] pub struct Tooth { quadrant: QuadrantKind, number: u8,