use std::fmt::Write;
use crate::format::hex;
pub fn apply(hint: &str, data: &[u8]) -> String {
if hint.is_empty() || data.is_empty() {
return hex::encode(data);
}
let hint = hint.as_bytes();
let mut result = String::with_capacity(data.len() * 4);
let mut hint_pos = 0;
let mut data_pos = 0;
let mut last_spec_start = 0;
while data_pos < data.len() {
if hint_pos >= hint.len() {
hint_pos = last_spec_start;
}
let spec_start = hint_pos;
let star_prefix = if hint_pos < hint.len() && hint[hint_pos] == b'*' {
hint_pos += 1;
true
} else {
false
};
if hint_pos >= hint.len() || !is_digit(hint[hint_pos]) {
return hex::encode(data);
}
let mut take = 0usize;
while hint_pos < hint.len() && is_digit(hint[hint_pos]) {
take = take * 10 + (hint[hint_pos] - b'0') as usize;
hint_pos += 1;
}
if take == 0 {
return hex::encode(data);
}
if hint_pos >= hint.len() {
return hex::encode(data);
}
let fmt_char = hint[hint_pos];
if !matches!(fmt_char, b'd' | b'x' | b'o' | b'a' | b't') {
return hex::encode(data);
}
hint_pos += 1;
let (sep, has_sep) =
if hint_pos < hint.len() && !is_digit(hint[hint_pos]) && hint[hint_pos] != b'*' {
let s = hint[hint_pos];
hint_pos += 1;
(s, true)
} else {
(0, false)
};
let (term, has_term) = if star_prefix
&& hint_pos < hint.len()
&& !is_digit(hint[hint_pos])
&& hint[hint_pos] != b'*'
{
let t = hint[hint_pos];
hint_pos += 1;
(t, true)
} else {
(0, false)
};
last_spec_start = spec_start;
let repeat_count = if star_prefix && data_pos < data.len() {
let count = data[data_pos] as usize;
data_pos += 1;
count
} else {
1
};
for r in 0..repeat_count {
if data_pos >= data.len() {
break;
}
let end = (data_pos + take).min(data.len());
let chunk = &data[data_pos..end];
match fmt_char {
b'd' => {
let val = chunk.iter().fold(0u64, |acc, &b| (acc << 8) | u64::from(b));
let _ = write!(result, "{}", val);
}
b'x' => {
hex::write_to(&mut result, chunk);
}
b'o' => {
let val = chunk.iter().fold(0u64, |acc, &b| (acc << 8) | u64::from(b));
let _ = write!(result, "{:o}", val);
}
b'a' => {
for &b in chunk {
result.push(b as char);
}
}
b't' => {
result.push_str(&String::from_utf8_lossy(chunk));
}
_ => unreachable!(),
}
data_pos = end;
let more_data = data_pos < data.len();
if has_sep && more_data {
if has_term && r == repeat_count - 1 {
} else {
result.push(sep as char);
}
}
}
if has_term && data_pos < data.len() {
result.push(term as char);
}
}
result
}
#[inline]
fn is_digit(c: u8) -> bool {
c.is_ascii_digit()
}
pub fn apply_integer(hint: &str, value: i32) -> Option<String> {
match hint {
"x" => Some(format!("{:x}", value)),
"o" => Some(format!("{:o}", value)),
"b" => Some(format!("{:b}", value)),
"d" => Some(format!("{}", value)),
hint if hint.starts_with("d-") => {
let places: usize = hint[2..].parse().ok()?;
if places == 0 {
return Some(format!("{}", value));
}
Some(format_with_decimal_point(value, places))
}
_ => None,
}
}
fn format_with_decimal_point(value: i32, places: usize) -> String {
let is_negative = value < 0;
let abs_value = value.unsigned_abs();
let abs_str = abs_value.to_string();
let result = if abs_str.len() <= places {
let zeros_needed = places - abs_str.len();
format!("0.{}{}", "0".repeat(zeros_needed), abs_str)
} else {
let split_point = abs_str.len() - places;
let (integer_part, decimal_part) = abs_str.split_at(split_point);
format!("{}.{}", integer_part, decimal_part)
};
if is_negative {
format!("-{}", result)
} else {
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_hint_returns_hex() {
assert_eq!(apply("", &[0x01, 0x02, 0x03]), "010203");
}
#[test]
fn empty_data_returns_empty() {
assert_eq!(apply("1d", &[]), "");
}
#[test]
fn ipv4_address() {
assert_eq!(apply("1d.1d.1d.1d", &[192, 168, 1, 1]), "192.168.1.1");
}
#[test]
fn ipv4_with_zone_id() {
assert_eq!(
apply("1d.1d.1d.1d%4d", &[192, 168, 1, 1, 0, 0, 0, 3]),
"192.168.1.1%3"
);
}
#[test]
fn mac_address() {
assert_eq!(
apply("1x:", &[0x00, 0x1a, 0x2b, 0x3c, 0x4d, 0x5e]),
"00:1a:2b:3c:4d:5e"
);
}
#[test]
fn ipv6_address() {
let data = [
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01,
];
assert_eq!(
apply("2x:2x:2x:2x:2x:2x:2x:2x", &data),
"2001:0db8:0000:0000:0000:0000:0000:0001"
);
}
#[test]
fn ipv6_with_zone_id() {
let data = [
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x05,
];
assert_eq!(
apply("2x:2x:2x:2x:2x:2x:2x:2x%4d", &data),
"fe80:0000:0000:0000:0000:0000:0000:0001%5"
);
}
#[test]
fn display_string() {
assert_eq!(apply("255a", b"Hello, World!"), "Hello, World!");
}
#[test]
fn simple_decimal() {
assert_eq!(apply("1d", &[42]), "42");
}
#[test]
fn multi_byte_decimal() {
assert_eq!(apply("4d", &[0x00, 0x01, 0x00, 0x00]), "65536");
}
#[test]
fn octal_format() {
assert_eq!(apply("1o", &[8]), "10");
}
#[test]
fn hex_with_dash_separator() {
assert_eq!(apply("1x-", &[0xaa, 0xbb, 0xcc]), "aa-bb-cc");
}
#[test]
fn star_prefix_repeat() {
assert_eq!(apply("*1x:", &[3, 0xaa, 0xbb, 0xcc]), "aa:bb:cc");
}
#[test]
fn star_prefix_with_terminator() {
assert_eq!(apply("*1d./1d", &[3, 10, 20, 30, 40]), "10.20.30/40");
}
#[test]
fn trailing_separator_suppressed() {
assert_eq!(apply("1d.", &[1, 2, 3]), "1.2.3");
}
#[test]
fn date_and_time() {
assert_eq!(
apply("2d-1d-1d,1d:1d:1d.1d", &[0x07, 0xE6, 8, 15, 8, 1, 15, 0]),
"2022-8-15,8:1:15.0"
);
}
#[test]
fn data_shorter_than_spec() {
assert_eq!(apply("1d.1d.1d.1d", &[10, 20]), "10.20");
}
#[test]
fn utf8_format() {
assert_eq!(apply("10t", b"hello"), "hello");
}
#[test]
fn utf8_multibyte_sequences() {
let bytes = "café".as_bytes();
assert_eq!(apply("255t", bytes), "café");
}
#[test]
fn utf8_multibyte_cjk() {
let bytes = "世界".as_bytes();
assert_eq!(apply("255t", bytes), "世界");
}
#[test]
fn utf8_invalid_bytes_replaced_with_replacement_char() {
let bytes = &[0xff, 0xfe];
assert_eq!(apply("2t", bytes), "\u{fffd}\u{fffd}");
}
#[test]
fn uuid_format() {
let data = [
0x12, 0x34, 0x56, 0x78, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x00, 0x11, 0x22, 0x33,
0x44, 0x55,
];
assert_eq!(
apply("4x-2x-2x-1x1x-6x", &data),
"12345678-abcd-ef01-2345-001122334455"
);
}
#[test]
fn ipv4_with_prefix() {
assert_eq!(apply("1d.1d.1d.1d/1d", &[10, 0, 0, 0, 24]), "10.0.0.0/24");
}
#[test]
fn two_digit_take_value() {
assert_eq!(
apply("10d", &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]),
"1"
);
}
#[test]
fn zero_padded_hex_output() {
assert_eq!(apply("1x", &[0x0f]), "0f");
}
#[test]
fn single_byte_trailing_separator_suppressed() {
assert_eq!(apply("1d.", &[42]), "42");
}
#[test]
fn invalid_format_character() {
assert_eq!(apply("1z", &[1, 2, 3]), "010203");
}
#[test]
fn missing_format_character() {
assert_eq!(apply("1", &[1, 2, 3]), "010203");
}
#[test]
fn missing_take_value() {
assert_eq!(apply("d", &[1, 2, 3]), "010203");
}
#[test]
fn zero_take_value() {
assert_eq!(apply("0d", &[1, 2, 3]), "010203");
}
#[test]
fn single_spec_repeats_for_all_data() {
assert_eq!(apply("1d.", &[1, 2, 3, 4, 5]), "1.2.3.4.5");
}
#[test]
fn last_spec_repeats_after_fixed_prefix() {
assert_eq!(apply("1d-1d.", &[1, 2, 3, 4, 5, 6]), "1-2.3.4.5.6");
}
#[test]
fn hex_implicit_repetition() {
assert_eq!(apply("1x:", &[0xaa, 0xbb, 0xcc, 0xdd]), "aa:bb:cc:dd");
}
#[test]
fn integer_hint_decimal() {
assert_eq!(apply_integer("d", 1234), Some("1234".to_string()));
assert_eq!(apply_integer("d", -42), Some("-42".to_string()));
assert_eq!(apply_integer("d", 0), Some("0".to_string()));
}
#[test]
fn integer_hint_hex() {
assert_eq!(apply_integer("x", 255), Some("ff".to_string()));
assert_eq!(apply_integer("x", 0), Some("0".to_string()));
assert_eq!(apply_integer("x", 16), Some("10".to_string()));
assert_eq!(apply_integer("x", -1), Some("ffffffff".to_string()));
}
#[test]
fn integer_hint_octal() {
assert_eq!(apply_integer("o", 8), Some("10".to_string()));
assert_eq!(apply_integer("o", 64), Some("100".to_string()));
assert_eq!(apply_integer("o", 0), Some("0".to_string()));
}
#[test]
fn integer_hint_binary() {
assert_eq!(apply_integer("b", 5), Some("101".to_string()));
assert_eq!(apply_integer("b", 255), Some("11111111".to_string()));
assert_eq!(apply_integer("b", 0), Some("0".to_string()));
}
#[test]
fn integer_hint_decimal_places() {
assert_eq!(apply_integer("d-2", 1234), Some("12.34".to_string()));
assert_eq!(apply_integer("d-1", 255), Some("25.5".to_string()));
assert_eq!(apply_integer("d-3", 12500), Some("12.500".to_string()));
assert_eq!(apply_integer("d-2", 5), Some("0.05".to_string()));
assert_eq!(apply_integer("d-2", 50), Some("0.50".to_string()));
assert_eq!(apply_integer("d-3", 5), Some("0.005".to_string()));
assert_eq!(apply_integer("d-2", 0), Some("0.00".to_string()));
assert_eq!(apply_integer("d-2", -500), Some("-5.00".to_string()));
assert_eq!(apply_integer("d-2", -5), Some("-0.05".to_string()));
assert_eq!(apply_integer("d-1", -42), Some("-4.2".to_string()));
assert_eq!(apply_integer("d-0", 1234), Some("1234".to_string()));
}
#[test]
fn integer_hint_invalid() {
assert_eq!(apply_integer("", 42), None);
assert_eq!(apply_integer("z", 42), None);
assert_eq!(apply_integer("d-abc", 42), None);
assert_eq!(apply_integer("1d", 42), None); }
}