#![no_std]
#![warn(clippy::indexing_slicing)]
mod extension_traits;
pub use extension_traits::{DisplayExt, IteratorExt};
fn checked_split_at(s: &[u8], index: usize) -> Option<(&[u8], &[u8])> {
Some((s.get(..index)?, s.get(index..)?))
}
#[test]
#[rustfmt::skip]
fn test_checked_split_at() {
assert_eq!(checked_split_at(b"", 0), Some((&b""[..], &b""[..])));
assert_eq!(checked_split_at(b"Hello", 0), Some((&b""[..], &b"Hello"[..])));
assert_eq!(checked_split_at(b"Hello", 3), Some((&b"Hel"[..], &b"lo"[..])));
assert_eq!(checked_split_at(b"Hello", 5), Some((&b"Hello"[..], &b""[..])));
assert_eq!(checked_split_at(b"Hello", 6), None);
}
#[doc(hidden)]
pub struct Concat2<A, B>(pub A, pub B);
impl<A: core::fmt::Display, B: core::fmt::Display> core::fmt::Display for Concat2<A, B> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
self.0.fmt(f)?;
self.1.fmt(f)?;
Ok(())
}
}
#[macro_export]
macro_rules! concat {
() => {
""
};
($e:expr $(,)?) => {
$e
};
($e:expr $(,$rest:expr)+ $(,)?) => {
$crate::Concat2($e, $crate::concat!($($rest),*))
};
}
pub fn unicode_block_bar(max_length: usize, proportion: f32) -> UnicodeBlockBar {
const BLOCK_CHARS: [&str; 9] = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"];
impl core::fmt::Display for UnicodeBlockBar {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
for _ in 0..self.num_full_blocks {
f.write_str(&BLOCK_CHARS[8])?;
}
f.write_str(self.midpoint)?;
for _ in 0..self.num_spaces {
f.write_str(&BLOCK_CHARS[0])?;
}
Ok(())
}
}
let max_steps = max_length * 8;
let steps = proportion * max_steps as f32;
let steps = (steps.max(0.0) as usize).min(max_steps);
if steps == max_steps {
UnicodeBlockBar {
num_full_blocks: max_length,
midpoint: "",
num_spaces: 0,
}
} else {
#[allow(clippy::indexing_slicing)] UnicodeBlockBar {
num_full_blocks: steps / 8,
midpoint: &BLOCK_CHARS[steps % 8],
num_spaces: max_length - (steps / 8 + 1),
}
}
}
pub struct UnicodeBlockBar {
num_full_blocks: usize,
midpoint: &'static str,
num_spaces: usize,
}
pub fn vertical_unicode_block_bars<I>(
max_height: usize,
proportions: I,
) -> VerticalUnicodeBlockBars<I::IntoIter>
where
I: IntoIterator<Item = f32>,
I::IntoIter: Clone,
{
const BLOCK_CHARS: [&str; 9] = [" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
impl<I: Iterator<Item = f32> + Clone> core::fmt::Display for VerticalUnicodeBlockBars<I> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let max_steps = self.max_height * 8;
for row in 0..self.max_height {
if row > 0 {
f.write_str("\n")?;
}
for proportion in self.proportions.clone() {
let steps = (1.0 - proportion) * max_steps as f32;
let steps = (steps.max(0.0) as usize).min(max_steps);
f.write_str(match row.cmp(&(steps / 8)) {
core::cmp::Ordering::Less => &BLOCK_CHARS[0],
#[allow(clippy::indexing_slicing)] core::cmp::Ordering::Equal => &BLOCK_CHARS[8 - steps % 8],
core::cmp::Ordering::Greater => &BLOCK_CHARS[8],
})?;
}
}
Ok(())
}
}
VerticalUnicodeBlockBars {
max_height,
proportions: proportions.into_iter(),
}
}
pub struct VerticalUnicodeBlockBars<I> {
max_height: usize,
proportions: I,
}
pub fn join<T, I, J>(iterator: I, joiner: J) -> Join<I::IntoIter, J>
where
T: core::fmt::Display,
I: IntoIterator<Item = T>,
I::IntoIter: Clone,
J: core::fmt::Display,
{
impl<T, I, J> core::fmt::Display for Join<I, J>
where
T: core::fmt::Display,
I: Iterator<Item = T> + Clone,
J: core::fmt::Display,
{
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let mut iter = self.iterator.clone();
if let Some(first_item) = iter.next() {
first_item.fmt(f)?;
}
for remaining_item in iter {
self.joiner.fmt(f)?;
remaining_item.fmt(f)?;
}
Ok(())
}
}
Join {
iterator: iterator.into_iter(),
joiner,
}
}
pub struct Join<I, J> {
iterator: I,
joiner: J,
}
pub fn join_format<I, J, C>(iterator: I, joiner: J, callback: C) -> JoinFormat<I::IntoIter, J, C>
where
I: IntoIterator,
I::IntoIter: Clone,
J: core::fmt::Display,
C: Fn(I::Item, &mut core::fmt::Formatter) -> core::fmt::Result,
{
impl<I, J, C> core::fmt::Display for JoinFormat<I, J, C>
where
I: Iterator + Clone,
J: core::fmt::Display,
C: Fn(I::Item, &mut core::fmt::Formatter) -> core::fmt::Result,
{
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let mut iter = self.iterator.clone();
if let Some(first_item) = iter.next() {
(self.callback)(first_item, f)?;
}
for remaining_item in iter {
self.joiner.fmt(f)?;
(self.callback)(remaining_item, f)?;
}
Ok(())
}
}
JoinFormat {
iterator: iterator.into_iter(),
callback,
joiner,
}
}
pub struct JoinFormat<I, J, C> {
iterator: I,
joiner: J,
callback: C,
}
pub fn repeat<T: core::fmt::Display>(token: T, times: usize) -> Repeat<T> {
impl<T: core::fmt::Display> core::fmt::Display for Repeat<T> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
for _ in 0..self.times {
write!(f, "{}", self.token)?;
}
Ok(())
}
}
Repeat { token, times }
}
pub struct Repeat<T> {
token: T,
times: usize,
}
pub fn lowercase<T: core::fmt::Display>(object: T) -> Lowercase<T> {
struct LowercaseWriter<'a, 'b> {
f: &'a mut core::fmt::Formatter<'b>,
}
impl core::fmt::Write for LowercaseWriter<'_, '_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for input_char in s.chars() {
write!(self.f, "{}", input_char.to_lowercase())?;
}
Ok(())
}
}
impl<T: core::fmt::Display> core::fmt::Display for Lowercase<T> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use core::fmt::Write as _;
write!(LowercaseWriter { f }, "{}", self.object)
}
}
Lowercase { object }
}
pub struct Lowercase<T: core::fmt::Display> {
object: T,
}
pub fn uppercase<T: core::fmt::Display>(object: T) -> Uppercase<T> {
struct UppercaseWriter<'a, 'b> {
f: &'a mut core::fmt::Formatter<'b>,
}
impl core::fmt::Write for UppercaseWriter<'_, '_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for input_char in s.chars() {
write!(self.f, "{}", input_char.to_uppercase())?;
}
Ok(())
}
}
impl<T: core::fmt::Display> core::fmt::Display for Uppercase<T> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use core::fmt::Write as _;
write!(UppercaseWriter { f }, "{}", self.object)
}
}
Uppercase { object }
}
pub struct Uppercase<T> {
object: T,
}
pub fn replace<'a, T: core::fmt::Display>(source: &'a str, from: &'a str, to: T) -> Replace<'a, T> {
impl<T: core::fmt::Display> core::fmt::Display for Replace<'_, T> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let mut last_end = 0;
for (start, part) in self.source.match_indices(self.from) {
#[allow(clippy::indexing_slicing)] f.write_str(&self.source[last_end..start])?;
write!(f, "{}", self.to)?;
last_end = start + part.len();
}
#[allow(clippy::indexing_slicing)] f.write_str(&self.source[last_end..])?;
Ok(())
}
}
Replace { source, from, to }
}
pub struct Replace<'a, T> {
source: &'a str,
from: &'a str,
to: T,
}
pub fn replace_n<'a, T>(source: &'a str, from: &'a str, to: T, n: usize) -> ReplaceN<'a, T>
where
T: core::fmt::Display,
{
impl<T: core::fmt::Display> core::fmt::Display for ReplaceN<'_, T> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let mut last_end = 0;
for (start, part) in self.source.match_indices(self.from).take(self.n) {
#[allow(clippy::indexing_slicing)] f.write_str(&self.source[last_end..start])?;
write!(f, "{}", self.to)?;
last_end = start + part.len();
}
#[allow(clippy::indexing_slicing)] f.write_str(&self.source[last_end..])?;
Ok(())
}
}
ReplaceN {
source,
from,
to,
n,
}
}
pub struct ReplaceN<'a, T> {
source: &'a str,
from: &'a str,
to: T,
n: usize,
}
pub fn concat<I>(iterator: I) -> Concat<I::IntoIter>
where
I: IntoIterator,
I::Item: core::fmt::Display,
I::IntoIter: Clone,
{
impl<I> core::fmt::Display for Concat<I>
where
I: Iterator + Clone,
I::Item: core::fmt::Display,
{
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
for item in self.iterator.clone() {
write!(f, "{}", item)?;
}
Ok(())
}
}
Concat {
iterator: iterator.into_iter(),
}
}
pub struct Concat<I> {
iterator: I,
}
pub fn collect_str_mut(
buf: &mut [u8],
object: impl core::fmt::Display,
) -> Result<&mut str, &mut str> {
use core::fmt::Write;
struct Cursor<'a> {
buf: &'a mut [u8],
ptr: usize,
}
impl Write for Cursor<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let s = s.as_bytes();
if self.ptr < self.buf.len() {
let bytes_to_write = usize::min(s.len(), self.buf.len() - self.ptr);
#[allow(clippy::indexing_slicing)] self.buf[self.ptr..(self.ptr + bytes_to_write)]
.copy_from_slice(&s[..bytes_to_write]);
}
self.ptr += s.len();
Ok(())
}
}
let mut cursor = Cursor { buf, ptr: 0 };
let _ = write!(cursor, "{}", object);
if cursor.ptr > cursor.buf.len() {
Err(match core::str::from_utf8_mut(&mut cursor.buf[..]) {
Ok(_) => core::str::from_utf8_mut(&mut cursor.buf[..]).unwrap(),
#[allow(clippy::indexing_slicing)] Err(err) => core::str::from_utf8_mut(&mut cursor.buf[..err.valid_up_to()]).unwrap(),
})
} else {
#[allow(clippy::indexing_slicing)] Ok(core::str::from_utf8_mut(&mut cursor.buf[..cursor.ptr]).unwrap())
}
}
pub fn collect_str(buf: &mut [u8], object: impl core::fmt::Display) -> Result<&str, &str> {
match collect_str_mut(buf, object) {
Ok(x) => Ok(x),
Err(x) => Err(x),
}
}
pub fn slice<T, R>(object: T, range: R) -> DisplaySlice<T>
where
T: core::fmt::Display,
R: core::ops::RangeBounds<usize>,
{
struct ExtractingWriter<'a, 'b> {
extract_range_start: usize,
extract_range_end: Option<usize>,
pointer: usize,
sink: &'a mut core::fmt::Formatter<'b>,
}
impl core::fmt::Write for ExtractingWriter<'_, '_> {
fn write_str(&mut self, segment: &str) -> core::fmt::Result {
let segment_slice_start = self
.extract_range_start
.saturating_sub(self.pointer)
.min(segment.len());
let segment_slice_end = match self.extract_range_end {
Some(extract_range_end) => extract_range_end
.saturating_sub(self.pointer)
.min(segment.len()),
None => segment.len(),
};
#[allow(clippy::indexing_slicing)] self.sink
.write_str(&segment[segment_slice_start..segment_slice_end])?;
self.pointer += segment.len();
Ok(())
}
}
impl<T: core::fmt::Display> core::fmt::Display for DisplaySlice<T> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use core::fmt::Write as _;
write!(
ExtractingWriter {
extract_range_start: self.extract_range_start,
extract_range_end: self.extract_range_end,
pointer: 0,
sink: f,
},
"{}",
self.object
)
}
}
DisplaySlice {
object,
extract_range_start: match range.start_bound() {
core::ops::Bound::Included(&x) => x,
core::ops::Bound::Excluded(&x) => x + 1,
core::ops::Bound::Unbounded => 0,
},
extract_range_end: match range.end_bound() {
core::ops::Bound::Included(&x) => Some(x + 1),
core::ops::Bound::Excluded(&x) => Some(x),
core::ops::Bound::Unbounded => None,
},
}
}
pub struct DisplaySlice<T> {
object: T,
extract_range_start: usize,
extract_range_end: Option<usize>,
}
#[allow(clippy::should_implement_trait)] pub fn cmp<T: core::fmt::Display>(this: T, other: &str) -> core::cmp::Ordering {
struct CompareWriter<'a> {
reference: &'a [u8],
state: core::cmp::Ordering,
}
impl core::fmt::Write for CompareWriter<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if self.state != core::cmp::Ordering::Equal {
return Ok(());
}
if let Some((reference_segment, rest)) = checked_split_at(self.reference, s.len()) {
self.state = s.as_bytes().cmp(reference_segment);
self.reference = rest;
} else {
self.state = core::cmp::Ordering::Greater;
};
Ok(())
}
}
let mut compare_writer = CompareWriter {
reference: other.as_bytes(),
state: core::cmp::Ordering::Equal,
};
use core::fmt::Write as _;
let _ = write!(compare_writer, "{}", this);
if compare_writer.state == core::cmp::Ordering::Less && !compare_writer.reference.is_empty() {
core::cmp::Ordering::Less
} else {
compare_writer.state
}
}
pub fn ordinal(number: i32) -> Ordinal {
impl core::fmt::Display for Ordinal {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let n = self.number.abs();
let suffix = match n % 10 {
1 if n % 100 != 11 => "st",
2 if n % 100 != 12 => "nd",
3 if n % 100 != 13 => "rd",
_ => "th",
};
write!(f, "{}{}", self.number, suffix)
}
}
Ordinal { number }
}
pub struct Ordinal {
number: i32,
}