diff --git a/src/cli.rs b/src/cli.rs index 01f98c4..9877e08 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -25,6 +25,7 @@ use crate::{ wav1d::Wav1D, tdm::Tdm, gap::Gap, + uc::Uc, }, }; @@ -86,6 +87,8 @@ enum Opt { Tdm, Gap, + + Uc, } diff --git a/src/commands/uc.rs b/src/commands/uc.rs index ce9fbef..6593bc0 100644 --- a/src/commands/uc.rs +++ b/src/commands/uc.rs @@ -2,7 +2,9 @@ use std::sync::OnceLock; use std::hash::Hash; use std::collections::HashMap; use std::str::FromStr; +use std::fmt; +use clap::Args; use nom::{ branch::alt, bytes::complete::tag, @@ -23,9 +25,10 @@ use nom::{ }, IResult, }; - use anyhow::Error; + use crate::Result; +use crate::OptProcess; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] @@ -180,6 +183,39 @@ fn get_prefix_scale() -> &'static HashMap { } +fn get_prefix_str() -> &'static HashMap { + static INSTANCE: OnceLock> = OnceLock::new(); + INSTANCE.get_or_init(|| { + [ + (MetricPrefix::Atto, "a"), + (MetricPrefix::Femto, "f"), + (MetricPrefix::Pico, "p"), + (MetricPrefix::Nano, "n"), + (MetricPrefix::Micro, "μ"), + (MetricPrefix::Milli, "m"), + (MetricPrefix::One, ""), + (MetricPrefix::Kilo, "K"), + (MetricPrefix::Mega, "M"), + (MetricPrefix::Giga, "G"), + (MetricPrefix::Tera, "T"), + (MetricPrefix::Peta, "P"), + (MetricPrefix::Exa, "E"), + ].iter().cloned().collect() + }) +} + + +impl fmt::Display for MetricPrefix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { // print full form of prefix + write!(f, "{:?}", self) + } else { // abbreviative by default + write!(f, "{}", get_prefix_str()[self]) + } + } +} + + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] /// Energy units. pub enum Unit { @@ -212,6 +248,35 @@ pub enum Unit { } +fn get_unit_str() -> &'static HashMap { + static INSTANCE: OnceLock> = OnceLock::new(); + INSTANCE.get_or_init(|| { + [ + (Unit::ElectronVolt, "eV"), + (Unit::CaloriePerMole, "Cal/mol"), + (Unit::JoulePerMole, "J/mol"), + (Unit::Kelvin, "K"), + (Unit::Hartree, "Ha"), + (Unit::Wavenumber, "cm-1"), + (Unit::Meter, "m"), + (Unit::Hertz, "Hz"), + (Unit::Second, "s"), + ].iter().cloned().collect() + }) +} + + +impl fmt::Display for Unit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + write!(f, "{:?}", self) + } else { + write!(f, "{}", get_unit_str()[self]) + } + } +} + + impl Unit { fn parse_unit(i: &str) -> IResult<&str, Unit> { use Unit::*; @@ -282,7 +347,7 @@ fn get_ratio_ev_to_other() -> &'static HashMap { } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] /// Each energy quantity should contains three parts: number, prefix and unit. pub struct Quantity { /// Singular float number @@ -305,6 +370,17 @@ impl FromStr for Quantity { } +impl fmt::Display for Quantity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + write!(f, "{:11.6} {:#} {:#}", self.number, self.prefix, self.unit) + } else { + write!(f, "{:11.6} {:}{:}", self.number, self.prefix, self.unit) + } + } +} + + impl Quantity { pub fn parse_quantity(i: &str) -> Result { match Self::parse_quantity_helper(i) { @@ -343,7 +419,7 @@ impl Quantity { .normalize_unit() } - fn normalize_prefix(mut self) -> Self { + pub fn normalize_prefix(mut self) -> Self { let scale = get_prefix_scale()[&self.prefix]; self.number *= scale; self.prefix = MetricPrefix::One; @@ -396,7 +472,17 @@ impl Quantity { self = self.normalize_prefix(); let number = self.number; let prefix = match number { - x if x <= 1E-18 => Atto, + x if x <= 1E-15 => Atto, + x if x <= 1E-12 => Femto, + x if x <= 1E-9 => Pico, + x if x <= 1E-6 => Nano, + x if x <= 1E-3 => Micro, + x if x <= 1E0 => Milli, + x if x <= 1E3 => One, + x if x <= 1E6 => Kilo, + x if x <= 1E9 => Mega, + x if x <= 1E12 => Giga, + x if x <= 1E15 => Tera, _ => Exa, }; @@ -446,6 +532,32 @@ fn double(i: &str) -> IResult<&str, f64> { } +#[derive(Debug, Args)] +/// Conversion between various energy units. +pub struct Uc { + /// Input energy quantity to be converted. Multiple input are supported. + pub input: Vec, +} + + +impl OptProcess for Uc { + fn process(&self) -> Result<()> { + for i in self.input.iter() { + println!("==================== Processing input \"{}\" ====================", i); + + let q = Quantity::from_str(i)?; + for q_unit in get_unit_str().keys().map(|u| q.to_quantity(*u)) { + println!(" {} == {}", q, q_unit); + } + + println!("================================================================================"); + println!(); + } + Ok(()) + } +} + + #[cfg(test)] mod tests { use super::*;