Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 64 additions & 7 deletions crates/common/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,37 @@ impl FormatSpec {
Ok(self.format_sign_and_align(&AsciiStr::new(&magnitude_str), "", FormatAlign::Right))
}

/// Whether the spec carries nothing at all, which is what `format(value, "")`
/// parses to. Written as a destructure so a new field cannot be forgotten here.
fn is_empty(&self) -> bool {
let Self {
conversion,
fill,
align,
align_specified,
sign,
no_neg_0,
alternate_form,
width,
grouping_option,
precision,
frac_grouping_option,
format_type,
} = self;
conversion.is_none()
&& fill.is_none()
&& align.is_none()
&& !align_specified
&& sign.is_none()
&& !no_neg_0
&& !alternate_form
&& width.is_none()
&& grouping_option.is_none()
&& precision.is_none()
&& frac_grouping_option.is_none()
&& format_type.is_none()
}

pub fn format_bool(&self, input: bool) -> Result<String, FormatSpecError> {
let x = u8::from(input);
match &self.format_type {
Expand All @@ -844,13 +875,11 @@ impl FormatSpec {
Some(FormatType::Exponent(_) | FormatType::FixedPoint(_) | FormatType::Percentage) => {
self.format_float(x as f64)
}
None => {
if self.no_neg_0 {
return Err(FormatSpecError::NegativeZeroCoercionNotAllowed("integer"));
}
let first_letter = (input.to_string().as_bytes()[0] as char).to_uppercase();
Ok(first_letter.collect::<String>() + &input.to_string()[1..])
}
// Only the empty spec spells the value out. Everything else without a
// presentation type, a width or an alignment included, formats `bool`
// the way it formats the `int` it is.
None if self.is_empty() => Ok(if input { "True" } else { "False" }.to_owned()),
None => self.format_int(&BigInt::from_u8(x).unwrap()),
Some(format_type) => {
let ch = char::from(format_type);
Err(FormatSpecError::UnknownFormatCode(ch, "bool"))
Expand Down Expand Up @@ -1769,6 +1798,34 @@ mod tests {
assert_eq!(format_bool("%", false), Ok("0.000000%".to_owned()));
}

#[test]
fn format_bool_without_a_presentation_type() {
// The bare spec is the only one that spells the value out.
assert_eq!(format_bool("", true), Ok("True".to_owned()));
assert_eq!(format_bool("", false), Ok("False".to_owned()));

// Anything else formats the integer, the way `int.__format__` would.
assert_eq!(format_bool("5", true), Ok(" 1".to_owned()));
assert_eq!(format_bool("<5", true), Ok("1 ".to_owned()));
assert_eq!(format_bool(">5", false), Ok(" 0".to_owned()));
assert_eq!(format_bool("^5", true), Ok(" 1 ".to_owned()));
assert_eq!(format_bool("05", true), Ok("00001".to_owned()));
assert_eq!(format_bool("+", true), Ok("+1".to_owned()));
assert_eq!(format_bool(" ", false), Ok(" 0".to_owned()));
assert_eq!(format_bool(",", true), Ok("1".to_owned()));
assert_eq!(format_bool("<", true), Ok("1".to_owned()));

// And it inherits the integer rules, precision included.
assert_eq!(
format_bool(".2", true),
Err(FormatSpecError::PrecisionNotAllowed)
);
assert_eq!(
format_bool("z", true),
Err(FormatSpecError::NegativeZeroCoercionNotAllowed("integer"))
);
}

#[test]
fn format_string_zero_padding_uses_left_alignment() {
let spec = FormatSpec::parse("08s").unwrap();
Expand Down
34 changes: 34 additions & 0 deletions extra_tests/snippets/builtin_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,37 @@ def test_zero_padding():
assert "{:.3g}".format(1234.5) == "1.23e+03"
assert f"{float('nan'):.10f}" == "nan"
assert f"{float('inf'):.10f}" == "inf"

# bool has no __format__ of its own, so the int rules apply. Only the bare
# spec spells the value out.
assert format(True, "") == "True"
assert format(False, "") == "False"
assert f"{True}" == "True"
assert f"{True:}" == "True"

assert format(True, "5") == " 1"
assert format(True, "<5") == "1 "
assert format(False, ">5") == " 0"
assert format(True, "^5") == " 1 "
assert format(True, "=5") == " 1"
assert format(True, "05") == "00001"
assert format(False, "05") == "00000"
assert format(True, "+") == "+1"
assert format(False, " ") == " 0"
assert format(True, ",") == "1"
assert format(True, "<") == "1"
assert "{:>6}|{:^6}".format(True, False) == " 1| 0 "

# The presentation types were already right, and stay right.
assert format(True, "d") == "1"
assert format(True, "#b") == "0b1"
assert format(False, "x") == "0"
assert format(True, "c") == "\x01"
assert format(True, "e") == "1.000000e+00"
assert format(True, "%") == "100.000000%"

# Precision belongs to no integer spec, bool included.
assert_raises(ValueError, format, True, ".2")
assert_raises(ValueError, format, True, "5.2")
assert_raises(ValueError, format, True, "z")
assert_raises(ValueError, format, True, "s")
Loading