use regex::Regex;
use std::{cmp::Ordering, path::PathBuf};
use crate::{
io::{Read, Seek, SeekFrom},
DiskImageError,
};
pub const CRC_CCITT_INITIAL: u16 = 0xFFFF;
pub(crate) fn get_length<T: Seek>(source: &mut T) -> Result<u64, crate::io::Error> {
let length = source.seek(SeekFrom::End(0))?;
source.seek(SeekFrom::Start(0))?;
Ok(length)
}
pub(crate) fn read_ascii<T: Read>(
source: &mut T,
terminator: Option<u8>,
max_len: Option<usize>,
) -> (Option<String>, u8) {
let mut string = String::new();
let byte_iter = source.bytes();
let terminator = terminator.unwrap_or(0);
let mut terminating_byte = 0;
for (i, byte) in byte_iter.enumerate() {
match byte {
Ok(b) => {
if b == terminator || b == 0 {
terminating_byte = b;
break;
}
else if b >= 32 && b.is_ascii() {
string.push(b as char);
}
}
Err(_) => return (None, 0),
}
if i == max_len.unwrap_or(usize::MAX) {
break;
}
}
if string.is_empty() {
(None, terminating_byte)
}
else {
(Some(string), terminating_byte)
}
}
pub fn crc_ibm_3740(data: &[u8], start: Option<u16>) -> u16 {
const POLY: u16 = 0x1021; let mut crc: u16 = start.unwrap_or(0xFFFF);
for &byte in data {
crc ^= (byte as u16) << 8;
for _ in 0..8 {
if (crc & 0x8000) != 0 {
crc = (crc << 1) ^ POLY;
}
else {
crc <<= 1;
}
}
}
crc
}
pub fn crc_ibm_3740_byte(byte: u8, crc: u16) -> u16 {
const POLY: u16 = 0x1021; let mut crc = crc;
crc ^= (byte as u16) << 8;
for _ in 0..8 {
if (crc & 0x8000) != 0 {
crc = (crc << 1) ^ POLY;
}
else {
crc <<= 1;
}
}
crc
}
pub fn dump_slice<W: crate::io::Write>(
data_slice: &[u8],
start_address: usize,
bytes_per_row: usize,
mut out: W,
) -> Result<(), DiskImageError> {
let rows = data_slice.len() / bytes_per_row;
let last_row_size = data_slice.len() % bytes_per_row;
for r in 0..rows {
out.write_fmt(format_args!("{:05X} | ", r * bytes_per_row + start_address))
.unwrap();
for b in 0..bytes_per_row {
out.write_fmt(format_args!("{:02X} ", data_slice[r * bytes_per_row + b]))
.unwrap();
}
out.write_fmt(format_args!("| ")).unwrap();
for b in 0..bytes_per_row {
let byte = data_slice[r * bytes_per_row + b];
out.write_fmt(format_args!(
"{}",
if (40..=126).contains(&byte) { byte as char } else { '.' }
))
.unwrap();
}
out.write_fmt(format_args!("\n")).unwrap();
}
if last_row_size > 0 {
out.write_fmt(format_args!("{:05X} | ", rows * bytes_per_row)).unwrap();
for b in 0..bytes_per_row {
if b < last_row_size {
out.write_fmt(format_args!("{:02X} ", data_slice[rows * bytes_per_row + b]))
.unwrap();
}
else {
out.write_fmt(format_args!(" ")).unwrap();
}
}
out.write_fmt(format_args!("| ")).unwrap();
for b in 0..bytes_per_row {
if b < last_row_size {
let byte = data_slice[rows * bytes_per_row + b];
out.write_fmt(format_args!(
"{}",
if (40..=126).contains(&byte) { byte as char } else { '.' }
))
.unwrap();
}
else {
out.write_fmt(format_args!(" ")).unwrap();
}
}
out.write_fmt(format_args!("\n")).unwrap();
}
Ok(())
}
pub fn dump_string(data_slice: &[u8]) -> String {
let mut out = String::new();
for &byte in data_slice {
out.push(if (40..=126).contains(&byte) { byte as char } else { '.' });
}
out
}
#[allow(clippy::ptr_arg)]
pub fn natural_sort(a: &PathBuf, b: &PathBuf) -> Ordering {
let re = Regex::new(r"(\D+)|(\d+)").expect("Invalid regex");
let a_str = a.iter().next().and_then(|s| s.to_str()).unwrap_or("");
let b_str = b.iter().next().and_then(|s| s.to_str()).unwrap_or("");
let a_parts = re.captures_iter(a_str);
let b_parts = re.captures_iter(b_str);
for (a_part, b_part) in a_parts.zip(b_parts) {
if let (Some(a_text), Some(b_text)) = (a_part.get(1), b_part.get(1)) {
let ordering = a_text.as_str().to_lowercase().cmp(&b_text.as_str().to_lowercase());
if ordering != Ordering::Equal {
return ordering;
}
continue;
}
let a_num = a_part.get(2).and_then(|m| m.as_str().parse::<u32>().ok());
let b_num = b_part.get(2).and_then(|m| m.as_str().parse::<u32>().ok());
match (a_num, b_num) {
(Some(a_num), Some(b_num)) => {
let ordering = a_num.cmp(&b_num);
if ordering != Ordering::Equal {
return ordering;
}
}
_ => return a_str.to_lowercase().cmp(&b_str.to_lowercase()),
}
}
a_str.to_lowercase().cmp(&b_str.to_lowercase())
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_natural_sort() {
let mut paths = vec![
PathBuf::from("Disk1"),
PathBuf::from("disk10"),
PathBuf::from("Disk2"),
PathBuf::from("Disk3"),
PathBuf::from("disk11"),
PathBuf::from("Disk12"),
PathBuf::from("Disk9"),
];
paths.sort_by(natural_sort);
let expected_order = vec![
PathBuf::from("Disk1"),
PathBuf::from("Disk2"),
PathBuf::from("Disk3"),
PathBuf::from("Disk9"),
PathBuf::from("disk10"),
PathBuf::from("disk11"),
PathBuf::from("Disk12"),
];
assert_eq!(paths, expected_order);
}
#[test]
fn test_natural_sort_with_paths() {
let mut paths = vec![
PathBuf::from("Disk10/track00.0.raw"),
PathBuf::from("Disk11/track00.0.raw"),
PathBuf::from("Disk12/track00.0.raw"),
PathBuf::from("Disk13/track00.0.raw"),
PathBuf::from("Disk14/track00.0.raw"),
PathBuf::from("Disk15/track00.0.raw"),
PathBuf::from("Disk1/track00.0.raw"),
PathBuf::from("Disk2/track00.0.raw"),
PathBuf::from("Disk3/track00.0.raw"),
PathBuf::from("Disk4/track00.0.raw"),
PathBuf::from("Disk5/track00.0.raw"),
PathBuf::from("Disk6/track00.0.raw"),
];
paths.sort_by(natural_sort);
let expected_order = vec![
PathBuf::from("Disk1/track00.0.raw"),
PathBuf::from("Disk2/track00.0.raw"),
PathBuf::from("Disk3/track00.0.raw"),
PathBuf::from("Disk4/track00.0.raw"),
PathBuf::from("Disk5/track00.0.raw"),
PathBuf::from("Disk6/track00.0.raw"),
PathBuf::from("Disk10/track00.0.raw"),
PathBuf::from("Disk11/track00.0.raw"),
PathBuf::from("Disk12/track00.0.raw"),
PathBuf::from("Disk13/track00.0.raw"),
PathBuf::from("Disk14/track00.0.raw"),
PathBuf::from("Disk15/track00.0.raw"),
];
assert_eq!(paths, expected_order);
}
}