dfmt 0.4.1

A fully featured dynamic template drop in replacement for the format! macro.
Documentation
use core::fmt::Write;

use crate::{values::Specifier, ArgumentKey, Error};

#[cfg(not(feature = "std"))]
use alloc::{
    string::{String, ToString},
    vec::Vec,
};

#[derive(Debug, Clone)]
pub enum Piece {
    Literal(String),
    BracketOpen,
    BracketClose,
    Argument {
        key: ArgumentKey,
        specifier: Option<Specifier>,
    },
}

impl Piece {
    pub fn parse(input: &str) -> Result<Vec<Self>, Error> {
        let mut pieces: Vec<Piece> = Vec::with_capacity(10);

        let mut cursor = 0;
        let mut current_char = 0;
        let mut bracket = None;
        let mut separator = None;
        let mut internal_index = 0;

        let chars = input.as_bytes();
        while current_char < chars.len() {
            let char = chars[current_char];
            match char {
                b':' if bracket == Some(b'{') => {
                    if separator.is_some() {
                        return Err(Error::UnexpectedToken);
                    }

                    separator = Some(current_char);
                }
                b'{' | b'}' => match (bracket, char) {
                    (None, _) => {
                        if cursor < current_char {
                            pieces.push(Piece::Literal(input[cursor..current_char].to_string()));
                        }
                        bracket = Some(char);
                        cursor = current_char;
                    }
                    (Some(b'{'), b'}') => {
                        let specifier = if let Some(seperator_index) = separator {
                            Some(Specifier::parse(
                                &input[seperator_index + 1..current_char],
                                &mut internal_index,
                            )?)
                        } else {
                            None
                        };

                        let key = {
                            let (name_start, name_end) = match separator {
                                None => (cursor + 1, current_char),
                                Some(seperator_index) => (cursor + 1, seperator_index),
                            };

                            if name_start == name_end {
                                internal_index += 1;
                                ArgumentKey::Index(internal_index - 1)
                            } else {
                                match input[name_start..name_end].parse::<usize>() {
                                    Ok(arg_index) => ArgumentKey::Index(arg_index),
                                    Err(_) => {
                                        ArgumentKey::Name(input[name_start..name_end].to_string())
                                    }
                                }
                            }
                        };

                        pieces.push(Piece::Argument { key, specifier });

                        separator = None;
                        bracket = None;
                        cursor = current_char + 1;
                    }
                    (Some(b'{'), b'{') => {
                        pieces.push(Piece::BracketOpen);
                        bracket = None;
                        cursor = current_char + 1;
                    }
                    (Some(b'}'), b'}') => {
                        pieces.push(Piece::BracketClose);
                        bracket = None;
                        cursor = current_char + 1;
                    }
                    _ => {
                        return Err(Error::UnexpectedToken);
                    }
                },
                _ => {}
            }
            current_char += 1;
        }

        if cursor < current_char {
            pieces.push(Piece::Literal(input[cursor..current_char].to_string()));
        }

        if bracket.is_some() {
            Err(Error::UnexpectedToken)
        } else {
            Ok(pieces)
        }
    }
}

impl core::fmt::Display for Piece {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Piece::Literal(literal) => f.write_str(literal),
            Piece::BracketOpen => f.write_str("{{"),
            Piece::BracketClose => f.write_str("}}"),
            Piece::Argument { key, specifier } => {
                f.write_char('{')?;
                write!(f, "{}", key)?;
                if let Some(specifier) = specifier {
                    f.write_char(':')?;
                    write!(f, "{}", specifier)?;
                }
                f.write_char('}')
            }
        }
    }
}