use super::{
DocGen, helpers,
str::{CssStrRawFormatter, format_str, is_preferred_quote_allowed},
};
use crate::{ctx::Ctx, state::State};
use raffia::{Spanned, ast::*, token::TokenWithSpan};
use std::{borrow::Cow, mem};
use tiny_pretty::Doc;
impl<'a, 's: 'a> DocGen<'a, 's> for BracketBlock<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
let mut docs = itertools::intersperse(
self.value.iter().map(|value| value.doc(ctx, state)),
Doc::soft_line(),
)
.collect::<Vec<_>>();
docs.insert(0, Doc::char('['));
docs.push(Doc::char(']'));
Doc::list(docs)
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for Calc<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
let left = if let (
ComponentValue::Calc(Calc {
op:
CalcOperator {
kind: CalcOperatorKind::Plus | CalcOperatorKind::Minus,
..
},
..
}),
CalcOperatorKind::Multiply | CalcOperatorKind::Division,
) = (&*self.left, &self.op.kind)
{
Doc::char('(')
.append(Doc::line_or_nil())
.append(self.left.doc(ctx, state))
.nest(ctx.indent_width)
.append(Doc::line_or_nil())
.append(Doc::char(')'))
} else {
self.left.doc(ctx, state)
};
let right = if let (
CalcOperatorKind::Multiply | CalcOperatorKind::Division,
ComponentValue::Calc(Calc {
op:
CalcOperator {
kind: CalcOperatorKind::Plus | CalcOperatorKind::Minus,
..
},
..
}),
)
| (
CalcOperatorKind::Plus,
ComponentValue::Calc(Calc {
op:
CalcOperator {
kind: CalcOperatorKind::Minus,
..
},
..
}),
)
| (
CalcOperatorKind::Minus,
ComponentValue::Calc(Calc {
op:
CalcOperator {
kind: CalcOperatorKind::Plus | CalcOperatorKind::Minus,
..
},
..
}),
)
| (
CalcOperatorKind::Multiply,
ComponentValue::Calc(Calc {
op:
CalcOperator {
kind: CalcOperatorKind::Division,
..
},
..
}),
)
| (
CalcOperatorKind::Division,
ComponentValue::Calc(Calc {
op:
CalcOperator {
kind: CalcOperatorKind::Multiply | CalcOperatorKind::Division,
..
},
..
}),
) = (&self.op.kind, &*self.right)
{
Doc::char('(')
.append(Doc::line_or_nil())
.append(self.right.doc(ctx, state))
.nest(ctx.indent_width)
.append(Doc::line_or_nil())
.append(Doc::char(')'))
} else {
self.right.doc(ctx, state)
};
left.append(helpers::format_operator_prefix_space(ctx))
.concat(ctx.end_spaced_comments(
ctx.get_comments_between(self.left.span().end, self.op.span.start),
))
.append(self.op.doc(ctx, state))
.append(helpers::format_operator_suffix_space(ctx))
.concat(ctx.end_spaced_comments(
ctx.get_comments_between(self.op.span.end, self.right.span().start),
))
.append(right)
.group()
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for CalcOperator {
fn doc(&self, _: &Ctx<'a, 's>, _: &State) -> Doc<'s> {
Doc::text(match self.kind {
CalcOperatorKind::Plus => "+",
CalcOperatorKind::Minus => "-",
CalcOperatorKind::Multiply => "*",
CalcOperatorKind::Division => "/",
})
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for ComponentValue<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
match self {
ComponentValue::BracketBlock(bracket_block) => bracket_block.doc(ctx, state),
ComponentValue::Calc(calc) => calc.doc(ctx, state),
ComponentValue::Dimension(dimension) => dimension.doc(ctx, state),
ComponentValue::Delimiter(delimiter) => delimiter.doc(ctx, state),
ComponentValue::Function(function) => function.doc(ctx, state),
ComponentValue::HexColor(hex_color) => hex_color.doc(ctx, state),
ComponentValue::IdSelector(id_selector) => id_selector.doc(ctx, state),
ComponentValue::ImportantAnnotation(important) => important.doc(ctx, state),
ComponentValue::InterpolableIdent(interpolable_ident) => {
interpolable_ident.doc(ctx, state)
}
ComponentValue::InterpolableStr(interpolable_str) => interpolable_str.doc(ctx, state),
ComponentValue::LayerName(layer_name) => layer_name.doc(ctx, state),
ComponentValue::LessBinaryOperation(less_binary_operation) => {
less_binary_operation.doc(ctx, state)
}
ComponentValue::LessCondition(less_condition) => less_condition.doc(ctx, state),
ComponentValue::LessDetachedRuleset(less_detached_ruleset) => {
less_detached_ruleset.doc(ctx, state)
}
ComponentValue::LessEscapedStr(less_escaped_str) => less_escaped_str.doc(ctx, state),
ComponentValue::LessJavaScriptSnippet(less_js_snippet) => {
less_js_snippet.doc(ctx, state)
}
ComponentValue::LessList(less_list) => less_list.doc(ctx, state).nest(ctx.indent_width),
ComponentValue::LessMixinCall(less_mixin_call) => less_mixin_call.doc(ctx, state),
ComponentValue::LessNamespaceValue(less_namespace_value) => {
less_namespace_value.doc(ctx, state)
}
ComponentValue::LessNegativeValue(less_negative_value) => {
less_negative_value.doc(ctx, state)
}
ComponentValue::LessParenthesizedOperation(less_parenthesized_operation) => {
less_parenthesized_operation.doc(ctx, state)
}
ComponentValue::LessPercentKeyword(less_percent_keyword) => {
less_percent_keyword.doc(ctx, state)
}
ComponentValue::LessPropertyVariable(less_property_variable) => {
less_property_variable.doc(ctx, state)
}
ComponentValue::LessVariable(less_variable) => less_variable.doc(ctx, state),
ComponentValue::LessVariableVariable(less_variable_variable) => {
less_variable_variable.doc(ctx, state)
}
ComponentValue::Number(number) => number.doc(ctx, state),
ComponentValue::Percentage(percentage) => percentage.doc(ctx, state),
ComponentValue::Ratio(ratio) => ratio.doc(ctx, state),
ComponentValue::SassArbitraryArgument(sass_arbitrary_arg) => {
sass_arbitrary_arg.doc(ctx, state)
}
ComponentValue::SassBinaryExpression(sass_binary_expr) => {
sass_binary_expr.doc(ctx, state)
}
ComponentValue::SassKeywordArgument(sass_keyword_arg) => {
sass_keyword_arg.doc(ctx, state)
}
ComponentValue::SassList(sass_list) => sass_list.doc(ctx, state),
ComponentValue::SassMap(sass_map) => sass_map.doc(ctx, state),
ComponentValue::SassQualifiedName(sass_qualified_name) => {
sass_qualified_name.doc(ctx, state)
}
ComponentValue::SassNestingDeclaration(sass_nesting_decl) => {
sass_nesting_decl.doc(ctx, state)
}
ComponentValue::SassParenthesizedExpression(sass_parenthesized_expr) => {
sass_parenthesized_expr.doc(ctx, state)
}
ComponentValue::SassParentSelector(sass_parent_selector) => {
sass_parent_selector.doc(ctx, state)
}
ComponentValue::SassUnaryExpression(sass_unary_expr) => sass_unary_expr.doc(ctx, state),
ComponentValue::SassVariable(sass_variable) => sass_variable.doc(ctx, state),
ComponentValue::TokenWithSpan(token_with_span) => token_with_span.doc(ctx, state),
ComponentValue::UnicodeRange(unicode_range) => unicode_range.doc(ctx, state),
ComponentValue::Url(url) => url.doc(ctx, state),
}
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for Delimiter {
fn doc(&self, _: &Ctx<'a, 's>, _: &State) -> Doc<'s> {
match self.kind {
DelimiterKind::Comma => Doc::char(','),
DelimiterKind::Solidus => Doc::char('/'),
DelimiterKind::Semicolon => Doc::char(';'),
}
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for Dimension<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
let unit = match self.kind {
DimensionKind::Length => {
if self.unit.name.eq_ignore_ascii_case("Q") {
Doc::char('Q')
} else {
Doc::text(self.unit.raw.to_ascii_lowercase())
}
}
DimensionKind::Angle
| DimensionKind::Duration
| DimensionKind::Resolution
| DimensionKind::Flex => Doc::text(self.unit.raw.to_ascii_lowercase()),
DimensionKind::Frequency => {
if self.unit.name.eq_ignore_ascii_case("Hz") {
Doc::text("Hz")
} else if self.unit.name.eq_ignore_ascii_case("kHz") {
Doc::text("kHz")
} else {
Doc::text(self.unit.raw.to_ascii_lowercase())
}
}
DimensionKind::Unknown => self.unit.doc(ctx, state),
};
self.value.doc(ctx, state).append(unit)
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for Function<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
let mut docs = Vec::with_capacity(4);
docs.push(self.name.doc(ctx, state));
docs.push(Doc::char('('));
let mut pos = self.name.span().end;
let mut arg_docs = Vec::with_capacity(self.args.len() * 2);
arg_docs.push(Doc::line_or_nil());
let args_groups = self
.args
.split_inclusive(|arg| {
matches!(
arg,
ComponentValue::Delimiter(Delimiter {
kind: DelimiterKind::Comma | DelimiterKind::Semicolon,
..
})
)
})
.collect::<Vec<_>>();
fn format_group<'a, 's: 'a>(
group: &[ComponentValue<'s>],
pos: &mut usize,
has_more_than_one_group: bool,
ctx: &Ctx<'a, 's>,
state: &State,
) -> Doc<'s> {
Doc::list(group.iter().enumerate().fold(
Vec::with_capacity(group.len()),
|mut docs, (i, arg)| {
let arg_span = arg.span();
let prev_arg = if i == 0 { None } else { group.get(i - 1) };
if i > 0 && *pos < arg_span.start {
if has_more_than_one_group {
docs.push(Doc::space());
} else if matches!(prev_arg, Some(ComponentValue::Function(..))) {
docs.push(Doc::line_or_space());
} else {
docs.push(Doc::soft_line());
}
}
docs.extend(ctx.end_spaced_comments(
ctx.get_comments_between(mem::replace(pos, arg_span.end), arg_span.start),
));
match arg {
ComponentValue::Number(number)
if matches!(prev_arg, Some(ComponentValue::InterpolableIdent(..))) =>
{
docs.push(Doc::text(number.raw));
}
_ => docs.push(arg.doc(ctx, state)),
}
docs
},
))
}
let has_more_than_one_group = args_groups.len() > 1;
arg_docs.extend(itertools::intersperse(
args_groups.iter().map(|group| {
if let [
group @ ..,
ComponentValue::Delimiter(
delimiter @ Delimiter {
kind: DelimiterKind::Comma | DelimiterKind::Semicolon,
span: delimiter_span,
},
),
] = group
{
format_group(group, &mut pos, has_more_than_one_group, ctx, state)
.concat(ctx.start_spaced_comments(ctx.get_comments_between(
mem::replace(&mut pos, delimiter_span.end),
delimiter_span.start,
)))
.append(delimiter.doc(ctx, state))
} else {
format_group(group, &mut pos, has_more_than_one_group, ctx, state)
}
}),
helpers::get_smart_linebreak(
self.span.start,
&self.args,
ctx.options.function_args_prefer_single_line,
ctx,
),
));
let mut has_last_line_comment = false;
arg_docs.extend(ctx.start_spaced_comments_without_last_hard_line(
ctx.get_comments_between(pos, self.span.end),
&mut has_last_line_comment,
));
docs.push(
Doc::list(arg_docs)
.nest(ctx.indent_width)
.append(if has_last_line_comment {
Doc::hard_line()
} else {
Doc::line_or_nil()
})
.group(),
);
docs.push(Doc::char(')'));
Doc::list(docs)
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for FunctionName<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
match self {
FunctionName::Ident(ident) => ident.doc(ctx, state),
FunctionName::LessFormatFunction(less_format_fn) => less_format_fn.doc(ctx, state),
FunctionName::LessListFunction(less_list_fn) => less_list_fn.doc(ctx, state),
FunctionName::SassQualifiedName(sass_qualified_name) => {
sass_qualified_name.doc(ctx, state)
}
}
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for Ident<'s> {
fn doc(&self, _: &Ctx<'a, 's>, _: &State) -> Doc<'s> {
Doc::text(self.raw)
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for HexColor<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, _: &State) -> Doc<'s> {
Doc::text(format_hex_raw(self.raw, ctx))
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for InterpolableIdent<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
match self {
InterpolableIdent::Literal(literal) => literal.doc(ctx, state),
InterpolableIdent::SassInterpolated(sass_interpolated) => {
sass_interpolated.doc(ctx, state)
}
InterpolableIdent::LessInterpolated(less_interpolated) => {
less_interpolated.doc(ctx, state)
}
}
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for InterpolableIdentStaticPart<'s> {
fn doc(&self, _: &Ctx<'a, 's>, _: &State) -> Doc<'s> {
Doc::text(self.raw)
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for InterpolableStr<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
match self {
InterpolableStr::Literal(literal) => literal.doc(ctx, state),
InterpolableStr::SassInterpolated(sass_interpolated) => {
sass_interpolated.doc(ctx, state)
}
InterpolableStr::LessInterpolated(less_interpolated) => {
less_interpolated.doc(ctx, state)
}
}
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for InterpolableUrlStaticPart<'s> {
fn doc(&self, _: &Ctx<'a, 's>, _: &State) -> Doc<'s> {
Doc::text(self.raw)
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for Number<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, _: &State) -> Doc<'s> {
Doc::text(format_number_raw(self.raw, ctx))
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for Percentage<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
self.value.doc(ctx, state).append(Doc::char('%'))
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for Ratio<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
self.numerator
.doc(ctx, state)
.append(Doc::char('/'))
.append(self.denominator.doc(ctx, state))
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for Str<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, _: &State) -> Doc<'s> {
Doc::text(format_str(
self.raw,
CssStrRawFormatter::new(self.raw),
is_preferred_quote_allowed(self.raw, ctx.options.quotes),
ctx.options.quotes,
))
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for TokenWithSpan<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, _: &State) -> Doc<'s> {
use raffia::token::Token;
match &self.token {
Token::Ampersand(..) => Doc::char('&'),
Token::Asterisk(..) => Doc::char('*'),
Token::AsteriskEqual(..) => Doc::text("*="),
Token::At(..) => Doc::char('@'),
Token::AtKeyword(at_keyword) => Doc::text(format!("@{}", at_keyword.ident.raw)),
Token::AtLBraceVar(at_lbrace_var) => {
Doc::text(format!("@{}{}{}", '{', at_lbrace_var.ident.raw, '}'))
}
Token::BacktickCode(backtick_code) => Doc::text(format!("`{}`", backtick_code.raw)),
Token::Bar(..) => Doc::char('|'),
Token::BarBar(..) => Doc::text("||"),
Token::BarEqual(..) => Doc::text("|="),
Token::CaretEqual(..) => Doc::text("^="),
Token::Cdo(..) => Doc::text("<!--"),
Token::Cdc(..) => Doc::text("-->"),
Token::Colon(..) => Doc::char(':'),
Token::ColonColon(..) => Doc::text("::"),
Token::Comma(..) => Doc::char(','),
Token::Dedent(..) => unreachable!(),
Token::Dimension(dimension) => {
Doc::text(format!("{}{}", dimension.value.raw, dimension.unit.raw))
}
Token::DollarEqual(..) => Doc::text("$="),
Token::DollarLBraceVar(dollar_lbrace_var) => {
Doc::text(format!("${}{}{}", '{', dollar_lbrace_var.ident.raw, '}'))
}
Token::DollarVar(dollar_var) => Doc::text(format!("${}", dollar_var.ident.raw)),
Token::Dot(..) => Doc::char('.'),
Token::DotDotDot(..) => Doc::text("..."),
Token::Eof(..) => unreachable!(),
Token::Equal(..) => Doc::char('='),
Token::EqualEqual(..) => Doc::text("=="),
Token::Exclamation(..) => Doc::char('!'),
Token::ExclamationEqual(..) => Doc::text("!="),
Token::GreaterThan(..) => Doc::char('>'),
Token::GreaterThanEqual(..) => Doc::text(">="),
Token::Hash(hash) => Doc::text(format_hex_raw(hash.raw, ctx)),
Token::HashLBrace(..) => Doc::text("#{"),
Token::Ident(ident) => Doc::text(ident.raw),
Token::Indent(..) => unreachable!(),
Token::LBrace(..) => Doc::char('{'),
Token::LBracket(..) => Doc::char('['),
Token::LessThan(..) => Doc::char('<'),
Token::LessThanEqual(..) => Doc::text("<="),
Token::Linebreak(..) => unreachable!(),
Token::LParen(..) => Doc::char('('),
Token::Minus(..) => Doc::char('-'),
Token::Number(number) => Doc::text(number.raw),
Token::NumberSign(..) => Doc::char('#'),
Token::Percent(..) => Doc::char('%'),
Token::Percentage(percentage) => Doc::text(format!("{}%", percentage.value.raw)),
Token::Plus(..) => Doc::char('+'),
Token::PlusUnderscore(..) => Doc::text("+_"),
Token::Question(..) => Doc::char('?'),
Token::RBrace(..) => Doc::char('}'),
Token::RBracket(..) => Doc::char(']'),
Token::RParen(..) => Doc::char(')'),
Token::Semicolon(..) => Doc::char(';'),
Token::Solidus(..) => Doc::char('/'),
Token::Str(str) => Doc::text(format_str(
str.raw,
CssStrRawFormatter::new(str.raw),
is_preferred_quote_allowed(str.raw, ctx.options.quotes),
ctx.options.quotes,
)),
Token::StrTemplate(..) => unreachable!(),
Token::Tilde(..) => Doc::char('~'),
Token::TildeEqual(..) => Doc::text("~="),
Token::UrlRaw(..) | Token::UrlTemplate(..) => unreachable!(),
}
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for UnicodeRange<'s> {
fn doc(&self, _: &Ctx<'a, 's>, _: &State) -> Doc<'s> {
let mut s = format!("U+{}", self.start_raw);
if let Some(end_raw) = self.end_raw {
s.push('-');
s.push_str(end_raw);
}
s.make_ascii_uppercase();
Doc::text(s)
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for Url<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
let mut docs = Vec::with_capacity(3);
docs.push(Doc::text(format!(
"{}(",
self.name.raw.to_ascii_lowercase()
)));
let mut args = Vec::with_capacity(1);
if let Some(value) = &self.value {
args.push(value.doc(ctx, state));
if !self.modifiers.is_empty() {
args.push(Doc::line_or_space());
args.append(
&mut itertools::intersperse(
self.modifiers
.iter()
.map(|modifier| modifier.doc(ctx, state)),
Doc::soft_line(),
)
.collect(),
);
}
}
docs.push(Doc::list(args).group().nest(ctx.indent_width));
docs.push(Doc::char(')'));
Doc::list(docs)
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for UrlModifier<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
match self {
UrlModifier::Ident(ident) => ident.doc(ctx, state),
UrlModifier::Function(function) => function.doc(ctx, state),
}
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for UrlRaw<'s> {
fn doc(&self, _: &Ctx<'a, 's>, _: &State) -> Doc<'s> {
Doc::text(self.raw)
}
}
impl<'a, 's: 'a> DocGen<'a, 's> for UrlValue<'s> {
fn doc(&self, ctx: &Ctx<'a, 's>, state: &State) -> Doc<'s> {
match self {
UrlValue::Raw(raw) => raw.doc(ctx, state),
UrlValue::SassInterpolated(sass_interpolated) => sass_interpolated.doc(ctx, state),
UrlValue::Str(str) => str.doc(ctx, state),
UrlValue::LessEscapedStr(less_escaped_str) => less_escaped_str.doc(ctx, state),
}
}
}
fn format_hex_raw(raw: &str, ctx: &Ctx) -> String {
use crate::config::{HexCase, HexColorLength};
let chars = raw.chars().collect::<Vec<_>>();
let mut hex = if chars.iter().all(|c| c.is_ascii_hexdigit()) {
match (chars.as_slice(), &ctx.options.hex_color_length) {
([c1, c2, c3], Some(HexColorLength::Long)) => {
format!("#{c1}{c1}{c2}{c2}{c3}{c3}")
}
([c1, c2, c3, c4], Some(HexColorLength::Long)) => {
format!("#{c1}{c1}{c2}{c2}{c3}{c3}{c4}{c4}")
}
([c1, c2, c3, c4, c5, c6], Some(HexColorLength::Short))
if c1.eq_ignore_ascii_case(c2)
&& c3.eq_ignore_ascii_case(c4)
&& c5.eq_ignore_ascii_case(c6) =>
{
format!("#{c1}{c3}{c5}")
}
([c1, c2, c3, c4, c5, c6, c7, c8], Some(HexColorLength::Short))
if c1.eq_ignore_ascii_case(c2)
&& c3.eq_ignore_ascii_case(c4)
&& c5.eq_ignore_ascii_case(c6)
&& c7.eq_ignore_ascii_case(c8) =>
{
format!("#{c1}{c3}{c5}{c7}")
}
_ => format!("#{raw}"),
}
} else {
format!("#{raw}")
};
match ctx.options.hex_case {
HexCase::Ignore => {}
HexCase::Lower => {
hex.make_ascii_lowercase();
}
HexCase::Upper => {
hex.make_ascii_uppercase();
}
};
hex
}
fn format_number_raw<'a, 's: 'a>(raw: &'s str, ctx: &Ctx<'a, 's>) -> Cow<'s, str> {
let number = raw.strip_suffix('.').unwrap_or(raw);
#[allow(clippy::collapsible_else_if)]
let number: Cow<_> = if ctx.options.omit_number_leading_zero {
if let Some(number) = number.strip_prefix("0.") {
format!(".{number}").into()
} else if let Some(number) = number.strip_prefix("-0.") {
format!("-.{number}").into()
} else if let Some(number) = number.strip_prefix("+0.") {
format!("+.{number}").into()
} else {
number.into()
}
} else {
if let Some(number) = number.strip_prefix('.') {
format!("0.{number}").into()
} else if let Some(number) = number.strip_prefix("-.") {
format!("-0.{number}").into()
} else if let Some(number) = number.strip_prefix("+.") {
format!("+0.{number}").into()
} else {
number.into()
}
};
if let Some((coefficient, exponent)) = number.split_once(['e', 'E']) {
format!(
"{}e{exponent}",
coefficient.strip_suffix('.').unwrap_or(coefficient)
)
.into()
} else {
number
}
}