use std::io::{Error as IoError, ErrorKind as IoErrorKind};
#[cfg(not(any(feature = "async_std", feature = "async_tokio")))]
use std::io::{BufRead, BufReader, Read};
#[cfg(feature = "async_tokio")]
use tokio::io::{AsyncBufReadExt, AsyncRead as Read, BufReader};
#[cfg(feature = "async_std")]
use async_std::io::{prelude::BufReadExt, BufReader, Read};
use crate::ParseError;
#[derive(Eq, PartialEq)]
enum Encoding {
Utf8,
Utf16,
}
pub(crate) struct FileReader<R> {
buf: Vec<u8>,
encoding: Encoding,
#[cfg(feature = "async_std")]
inner: BufReader<R>,
#[cfg(feature = "async_tokio")]
inner: BufReader<R>,
#[cfg(not(any(feature = "async_std", feature = "async_tokio")))]
inner: BufReader<R>,
}
macro_rules! read_until {
($self:expr) => {{
#[cfg(not(any(feature = "async_std", feature = "async_tokio")))]
{
$self.inner.read_until(b'\n', &mut $self.buf)
}
#[cfg(any(feature = "async_std", feature = "async_tokio"))]
{
$self.inner.read_until(b'\n', &mut $self.buf).await
}
}};
}
#[allow(unused_macro_rules)]
macro_rules! impl_reader {
() => {
impl<R: Read> FileReader<R> {
impl_reader!(@NEW);
impl_reader!(@NEXT_LINE);
}
};
(async) => {
impl<R: Read + Unpin> FileReader<R> {
impl_reader!(@NEW);
impl_reader!(@ASYNC NEXT_LINE);
}
};
(@NEW) => {
pub(crate) fn new(src: R) -> Self {
Self {
buf: Vec::with_capacity(32),
encoding: Encoding::Utf8,
inner: BufReader::new(src),
}
}
};
(@NEXT_LINE) => {
pub(crate) fn next_line(&mut self) -> Result<usize, IoError> {
impl_reader!(@NEXT_LINE_BODY, self);
}
};
(@ASYNC NEXT_LINE) => {
pub(crate) async fn next_line(&mut self) -> Result<usize, IoError> {
impl_reader!(@NEXT_LINE_BODY, self);
}
};
(@NEXT_LINE_BODY, $self:ident) => {
loop {
$self.buf.clear();
let bytes = read_until!($self)?;
if bytes == 0 {
return Ok(bytes);
}
$self.truncate();
if !$self.buf.is_empty() {
return Ok(bytes);
}
}
};
}
#[cfg(not(any(feature = "async_std", feature = "async_tokio")))]
impl_reader!();
#[cfg(any(feature = "async_tokio", feature = "async_std"))]
impl_reader!(async);
impl<R> FileReader<R> {
#[allow(clippy::wrong_self_convention)]
pub(crate) fn is_initial_empty_line(&mut self) -> bool {
if self.buf.starts_with(&[239, 187, 191]) {
self.buf.rotate_left(3);
self.buf.len() == 3
} else {
if self.buf.starts_with(&[255, 254]) {
self.encoding = Encoding::Utf16;
let mut sub = 1;
if self.buf.len() >= 3 {
sub += 1 + 2
* (self.buf.len() >= 4 && self.buf[self.buf.len() - 2] == b'\r') as usize;
}
self.buf.rotate_left(1);
self.buf.truncate(self.buf.len() - sub);
self.decode_utf16();
}
self.buf.is_empty()
}
}
pub(crate) fn version(&self) -> Result<u8, ParseError> {
self.buf
.iter()
.position(|&byte| byte == b'o')
.and_then(|idx| {
self.buf[idx..]
.starts_with(b"osu file format v")
.then_some(idx + 17)
})
.map(|idx| {
let mut n = 0;
for byte in &self.buf[idx..] {
if !(b'0'..=b'9').contains(byte) {
break;
}
n = 10 * n + (*byte & 0xF);
}
n
})
.ok_or(ParseError::IncorrectFileHeader)
}
pub(crate) fn get_section(&self) -> Option<&[u8]> {
if self.buf[0] == b'[' {
if let Some(end) = self.buf[1..].iter().position(|&byte| byte == b']') {
return Some(&self.buf[1..=end]);
}
}
None
}
pub(crate) fn get_line(&self) -> Result<&str, ParseError> {
std::str::from_utf8(&self.buf)
.map_err(|e| ParseError::IoError(IoError::new(IoErrorKind::InvalidData, Box::new(e))))
}
pub(crate) fn get_line_ascii(&mut self) -> Result<&str, ParseError> {
self.buf.iter_mut().for_each(|byte| {
if *byte >= 128 {
*byte = b'?';
}
});
std::str::from_utf8(&self.buf)
.map_err(|e| ParseError::IoError(IoError::new(IoErrorKind::InvalidData, Box::new(e))))
}
pub(crate) fn split_colon(&self) -> Option<(&[u8], &str)> {
let idx = self.buf.iter().position(|&byte| byte == b':')?;
let front = &self.buf[..idx];
let back = std::str::from_utf8(&self.buf[idx + 1..]).ok()?;
Some((front, back.trim_start()))
}
fn truncate(&mut self) {
if self.encoding == Encoding::Utf16 {
self.decode_utf16();
}
if self.buf.starts_with(&[b'/', b'/']) {
return self.buf.clear();
}
let len = self
.buf
.windows(3)
.rev()
.step_by(2)
.zip(1..)
.find_map(|(window, i)| {
if window[1] == b'/' {
if window[0] == b'/' {
return Some(self.buf.len() - 2 * i - 1);
} else if window[2] == b'/' {
return Some(self.buf.len() - 2 * i);
}
}
None
})
.unwrap_or_else(|| match &self.buf[..] {
[.., b'\r', b'\n'] => self.buf.len() - 2,
[.., b'\n'] => self.buf.len() - 1,
_ => self.buf.len(),
});
let len = self.buf[..len]
.iter()
.enumerate()
.rev()
.find_map(|(i, byte)| (!matches!(byte, b' ' | b'\t')).then_some(i + 1))
.unwrap_or(0);
self.buf.truncate(len);
}
fn decode_utf16(&mut self) {
let limit = self.buf.len() / 2 + 1;
for i in 2..limit {
self.buf.swap(i, i * 2 - 1);
}
self.buf.truncate(limit);
self.buf.rotate_left(1);
self.buf.truncate(self.buf.len() - 1);
}
}