use core::fmt::Write;
use crate::{
values::{
Alignment, AlternateForm, PadZero, Precision, Sign, ToAlternateForm, ToPadZero, ToSign,
Type, Width,
},
ArgumentKey, Error,
};
#[cfg(not(feature = "std"))]
use alloc::string::ToString;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Specifier {
pub ty: Type,
pub alternate_form: AlternateForm,
pub fill_character: char,
pub alignment: Alignment,
pub sign: Sign,
pub pad_zero: PadZero,
pub width: Width,
pub precision: Precision,
}
impl Specifier {
pub fn parse(input: &str, internal_index: &mut usize) -> Result<Self, Error> {
let mut current_specifier_index = 0;
let chars = input.as_bytes();
let input_len = input.len();
let mut specifier = Specifier::default();
if input_len == 0 {
return Ok(specifier);
}
if let Some(fill_character) = parse_fill_character(&chars[current_specifier_index..]) {
current_specifier_index += 1;
specifier.fill_character = fill_character;
}
if let Some(alignment) = parse_alignment(&chars[current_specifier_index..]) {
current_specifier_index += 1;
specifier.alignment = alignment;
}
if let Some(sign) = parse_sign(&chars[current_specifier_index..]) {
current_specifier_index += 1;
specifier.sign = Sign::of(sign);
}
if let Some(alternate_form) = parse_alternate_form(&chars[current_specifier_index..]) {
current_specifier_index += 1;
specifier.alternate_form = AlternateForm::of(alternate_form);
}
if let Some(pad_zero) = parse_pad_zero(&chars[current_specifier_index..]) {
current_specifier_index += 1;
specifier.pad_zero = PadZero::of(pad_zero);
}
if let Some((width, incr_index)) = parse_width(
&chars[current_specifier_index..],
&input[current_specifier_index..],
) {
current_specifier_index += incr_index;
specifier.width = width;
}
if let Some((precision, incr_index)) = parse_precision(
&chars[current_specifier_index..],
&input[current_specifier_index..],
internal_index,
) {
current_specifier_index += incr_index;
specifier.precision = precision;
}
if let Some(ty) = parse_ty(&chars[current_specifier_index..]) {
current_specifier_index += 1;
specifier.ty = ty;
}
if current_specifier_index < input.len() {
Err(Error::UnexpectedToken)
} else {
Ok(specifier)
}
}
#[cfg(feature = "nightly_formatting_options")]
pub(crate) fn formatting_options(&self) -> core::fmt::FormattingOptions {
let mut options = core::fmt::FormattingOptions::new();
options
.fill(self.fill_character)
.align(match self.alignment {
Alignment::Left => Some(core::fmt::Alignment::Left),
Alignment::Right => Some(core::fmt::Alignment::Right),
Alignment::Center => Some(core::fmt::Alignment::Center),
Alignment::Auto => None,
})
.sign(match self.sign {
Sign::Plus => Some(core::fmt::Sign::Plus),
Sign::None => None,
})
.sign_aware_zero_pad(self.pad_zero == PadZero::Activated)
.alternate(self.alternate_form == AlternateForm::Activated)
.width(match self.width {
Width::Dynamic(_) => None,
Width::Fixed(amount) => Some(amount as u16),
})
.precision(match self.precision {
Precision::Dynamic(_) | Precision::Auto => None,
Precision::Fixed(amount) => Some(amount as u16),
});
options
}
pub fn ty(mut self, ty: Type) -> Self {
self.ty = ty;
self
}
pub fn alternate_form<K: ToAlternateForm>(mut self, alternate_form: K) -> Self {
self.alternate_form = alternate_form.to_alternate_form();
self
}
pub fn fill_character(mut self, fill_character: char) -> Self {
self.fill_character = fill_character;
self
}
pub fn alignment(mut self, alignment: Alignment) -> Self {
self.alignment = alignment;
self
}
pub fn sign<K: ToSign>(mut self, sign: K) -> Self {
self.sign = sign.to_sign();
self
}
pub fn pad_zero<K: ToPadZero>(mut self, pad_zero: K) -> Self {
self.pad_zero = pad_zero.to_pad_zero();
self
}
pub fn width(mut self, width: Width) -> Self {
self.width = width;
self
}
pub fn precision(mut self, precision: Precision) -> Self {
self.precision = precision;
self
}
}
impl Default for Specifier {
fn default() -> Self {
Self {
ty: Type::Display,
alternate_form: AlternateForm::Deactivated,
fill_character: ' ',
alignment: Alignment::Auto,
sign: Sign::None,
pad_zero: PadZero::Deactivated,
width: Width::Fixed(0),
precision: Precision::Auto,
}
}
}
impl core::fmt::Display for Specifier {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.fill_character)?;
write!(f, "{}", self.alignment)?;
if self.sign == Sign::Plus {
f.write_char('+')?;
}
if self.alternate_form == AlternateForm::Activated {
f.write_char('#')?;
}
if self.pad_zero == PadZero::Activated {
f.write_char('0')?;
}
write!(f, "{}", self.width)?;
write!(f, "{}", self.precision)?;
write!(f, "{}", self.ty)?;
Ok(())
}
}
fn parse_fill_character(chars: &[u8]) -> Option<char> {
match (chars.first(), chars.get(1)) {
(Some(chr), Some(b'<') | Some(b'^') | Some(b'>')) => Some(*chr as char),
_ => None,
}
}
fn parse_alignment(chars: &[u8]) -> Option<Alignment> {
match chars.first() {
Some(b'<') => Some(Alignment::Left),
Some(b'^') => Some(Alignment::Center),
Some(b'>') => Some(Alignment::Right),
_ => None,
}
}
fn parse_sign(chars: &[u8]) -> Option<bool> {
match chars.first() {
Some(b'+') => Some(true),
Some(b'-') => Some(false),
_ => None,
}
}
fn parse_alternate_form(chars: &[u8]) -> Option<bool> {
match chars.first() {
Some(b'#') => Some(true),
_ => None,
}
}
fn parse_pad_zero(chars: &[u8]) -> Option<bool> {
match chars.first() {
Some(b'0') => Some(true),
_ => None,
}
}
fn parse_width(chars: &[u8], input: &str) -> Option<(Width, usize)> {
match chars.first() {
Some(b'.') => None,
Some(&chr) => {
if (chr as char).is_ascii_digit() {
let mut until_index = 0;
while let Some(&add_char) = chars.get(until_index + 1) {
if !add_char.is_ascii_digit() {
break;
}
until_index += 1;
}
until_index += 1;
Some((
Width::Fixed(input[..until_index].parse::<u16>().unwrap()),
until_index,
))
} else {
input.find('$').map(|var_index| {
(
Width::Dynamic(ArgumentKey::Name(input[..var_index].to_string())),
var_index + 1,
)
})
}
}
None => None,
}
}
fn parse_precision(
chars: &[u8],
input: &str,
internal_index: &mut usize,
) -> Option<(Precision, usize)> {
match chars.first() {
Some(b'.') => match chars.get(1) {
Some(b'*') => {
*internal_index += 1;
Some((
Precision::Dynamic(ArgumentKey::Index(*internal_index - 1)),
2,
))
}
Some(&chr) => {
if (chr as char).is_ascii_digit() {
let mut until_index = 1;
while let Some(&add_char) = chars.get(until_index + 1) {
if !add_char.is_ascii_digit() {
break;
}
until_index += 1;
}
until_index += 1;
Some((
Precision::Fixed(input[1..until_index].parse::<u16>().unwrap()),
until_index,
))
} else {
input.find('$').map(|var_index| {
(
Precision::Dynamic(ArgumentKey::Name(input[1..var_index].to_string())),
var_index + 1,
)
})
}
}
None => None,
},
_ => None,
}
}
fn parse_ty(chars: &[u8]) -> Option<Type> {
match chars.first() {
Some(b'?') => Some(Type::Debug),
Some(b'b') => Some(Type::Binary),
Some(b'o') => Some(Type::Octal),
Some(b'e') => Some(Type::LowerExp),
Some(b'E') => Some(Type::UpperExp),
Some(b'x') => Some(Type::LowerHex),
Some(b'X') => Some(Type::UpperHex),
Some(b'p') => Some(Type::Pointer),
_ => None,
}
}