formatx 0.1.2

A macro for formatting non literal strings at runtime in Rust
Documentation
use std::fmt::{Debug, Display};

use crate::error::Error;

#[derive(Debug, Clone)]
pub struct FormatSpec {
    pub original: String,
    align: Option<(Option<String>, String)>,
    sign: Option<String>,
    hashtag: bool,
    zero: bool,
    width: Option<usize>,
    precision: Option<usize>,
    types: Option<String>,
}

impl FormatSpec {
    pub fn new(mut spec: String) -> Self {
        let original = spec.clone();

        let spec_copy = spec.clone();
        let align = if let Some(align) = spec_copy.get(0..1) {
            if align == "<" || align == "^" || align == ">" {
                spec.replace_range(0..1, "");
                Some((None, align.to_owned()))
            } else if let Some(align) = spec_copy.get(1..2) {
                if align == "<" || align == "^" || align == ">" {
                    spec.replace_range(0..2, "");
                    Some((
                        Some(spec_copy.get(0..1).unwrap().to_owned()),
                        align.to_owned(),
                    ))
                } else {
                    None
                }
            } else {
                None
            }
        } else {
            None
        };

        let spec_copy = spec.clone();
        let sign = if let Some(sign) = spec_copy.get(0..1) {
            if sign == "+" || sign == "-" {
                spec.replace_range(0..1, "");
                Some(sign.to_owned())
            } else {
                None
            }
        } else {
            None
        };

        let hashtag = if let Some(hastag) = spec.get(0..1) {
            if hastag == "#" {
                spec.replace_range(0..1, "");
                true
            } else {
                false
            }
        } else {
            false
        };

        let zero = if let Some(zero) = spec.get(0..1) {
            if zero == "0" {
                spec.replace_range(0..1, "");
                true
            } else {
                false
            }
        } else {
            false
        };

        let width = crate::utils::usize_token(&spec, 0);

        if let Some(x) = width {
            spec = spec.trim_start_matches(&x.to_string()).to_owned();
        }

        let precision = if let Some(point) = spec.get(0..1) {
            if point == "." {
                crate::utils::usize_token(&spec, 1)
            } else {
                None
            }
        } else {
            None
        };

        if let Some(x) = precision {
            spec = spec.trim_start_matches(&format!(".{}", x)).to_owned();
        }

        let types = if spec == "" {
            None
        } else {
            Some(spec.split(" ").nth(0).unwrap().to_owned())
        };

        Self {
            original,
            align,
            sign,
            hashtag,
            zero,
            width,
            precision,
            types,
        }
    }

    pub fn format<T: Display + Debug>(&self, value: T) -> String {
        let mut fmtval = format!("{}", value);

        if let Some(precision) = self.precision {
            if self.zero && self.hashtag {
                fmtval = format!("{:#0.1$}", value, precision);
            } else if self.hashtag {
                fmtval = format!("{:#.1$}", value, precision);
            } else {
                fmtval = format!("{:.1$}", value, precision);
            }
        }

        if let Some(types) = &self.types {
            if self.hashtag {
                if types == "?" {
                    return format!("{:#?}", value);
                } else if types == "x?" {
                    return format!("{:#x?}", value);
                } else if types == "X?" {
                    return format!("{:#X?}", value);
                }
            } else {
                if types == "?" {
                    return format!("{:?}", value);
                } else if types == "x?" {
                    return format!("{:x?}", value);
                } else if types == "X?" {
                    return format!("{:X?}", value);
                }
            }
        }

        if let Some(sign) = &self.sign {
            if sign == "+" && !self.zero {
                if crate::utils::is_number_and_positive(&fmtval) {
                    fmtval = "+".to_owned() + &fmtval;
                }
            }
        }

        match &self.align {
            Some((Some(fill), align)) => {
                fmtval = fmtval.trim().to_owned();
                let chars_count = fmtval.chars().count();
                let width = self.width.unwrap_or(0);

                if width > chars_count {
                    if align == "<" {
                        fmtval = fmtval + &fill.repeat(width - chars_count);
                    } else if align == "^" {
                        let factor = if chars_count % 2 == 0 { 0 } else { 1 };
                        let start = fill.repeat((width - chars_count - factor) / 2);
                        let end = fill.repeat((width - chars_count + factor) / 2);
                        fmtval = start + &fmtval + &end;
                    } else if align == ">" {
                        fmtval = fill.repeat(width - chars_count) + &fmtval;
                    }
                }
            }
            Some((None, align)) => {
                if align == "<" {
                    fmtval = format!("{:<1$}", fmtval, self.width.unwrap_or(0));
                } else if align == "^" {
                    fmtval = format!("{:^1$}", fmtval, self.width.unwrap_or(0));
                } else if align == ">" {
                    fmtval = format!("{:>1$}", fmtval, self.width.unwrap_or(0));
                }
            }
            None => (),
        }

        if let Some(width) = self.width {
            if crate::utils::is_number(&fmtval) {
                let chars_count = fmtval.chars().count();

                if self.zero && width > chars_count {
                    if let Some(sign) = &self.sign {
                        if sign == "+" {
                            if crate::utils::is_number_and_positive(&fmtval) {
                                fmtval =
                                    "+".to_owned() + &"0".repeat(width - chars_count - 1) + &fmtval;
                            } else {
                                fmtval = "-".to_owned()
                                    + &"0".repeat(width - chars_count)
                                    + &fmtval[1..];
                            }
                        } else if sign == "-" {
                            fmtval =
                                "-".to_owned() + &"0".repeat(width - chars_count) + &fmtval[1..];
                        }
                    } else {
                        if fmtval.starts_with("-") {
                            fmtval =
                                "-".to_owned() + &"0".repeat(width - chars_count) + &fmtval[1..];
                        } else {
                            fmtval = "0".repeat(width - chars_count) + &fmtval;
                        }
                    }
                } else if width > chars_count {
                    fmtval = " ".repeat(width - chars_count) + &fmtval;
                }
            } else {
                if self.zero && self.hashtag {
                    fmtval = format!("{:#01$}", fmtval, width);
                } else if self.zero {
                    fmtval = format!("{:01$}", fmtval, width);
                } else if self.hashtag {
                    fmtval = format!("{:#1$}", fmtval, width);
                } else {
                    fmtval = format!("{:1$}", fmtval, width);
                }
            }
        }

        fmtval
    }

    pub fn unsupported(&self) -> Result<(), Error> {
        if self.original.contains("$") {
            return Err(Error::new_ufs(
                "parameter setting through $ sign argument is not supported",
            ));
        }

        if self.original.contains(".*") {
            return Err(Error::new_ufs("asterisk .* formatting is not supported"));
        }

        if self.original.contains("o") {
            return Err(Error::new_ufs("o formatting is not supported"));
        }

        if self.original.contains("x") {
            return Err(Error::new_ufs("x formatting is not supported"));
        }

        if self.original.contains("X") {
            return Err(Error::new_ufs("X formatting is not supported"));
        }

        if self.original.contains("p") {
            return Err(Error::new_ufs("p formatting is not supported"));
        }

        if self.original.contains("b") {
            return Err(Error::new_ufs("b formatting is not supported"));
        }

        if self.original.contains("e") {
            return Err(Error::new_ufs("e formatting is not supported"));
        }

        if self.original.contains("E") {
            return Err(Error::new_ufs("E formatting is not supported"));
        }

        Ok(())
    }
}