use std::fmt::{self, Display, Formatter};
use std::fs::File;
use std::io::{ErrorKind, Read};
#[cfg(test)]
mod test;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Bom {
Null,
Bocu1,
Gb18030,
Scsu,
UtfEbcdic,
Utf1,
Utf7,
Utf8,
Utf16Be,
Utf16Le,
Utf32Be,
Utf32Le,
}
impl AsRef<str> for Bom {
fn as_ref(&self) -> &str {
match *self {
Bom::Null => "[not set]",
Bom::Bocu1 => "BOCU-1",
Bom::Gb18030 => "GB 18030",
Bom::Scsu => "SCSU",
Bom::UtfEbcdic => "UTF-EBCDIC",
Bom::Utf1 => "UTF-1",
Bom::Utf7 => "UTF-7",
Bom::Utf8 => "UTF-8",
Bom::Utf16Be => "UTF-16 (big-endian)",
Bom::Utf16Le => "UTF-16 (little-endian)",
Bom::Utf32Be => "UTF-32 (big-endian)",
Bom::Utf32Le => "UTF-32 (little-endian)",
}
}
}
impl Default for Bom {
fn default() -> Self {
Bom::Null
}
}
impl Display for Bom {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "{}", self.as_ref())
}
}
impl Eq for Bom {}
macro_rules! compare_slice {
($slice:ident, $len:expr, [$($index:expr => $byte:expr),+]) => {
$slice.len() >= $len $(&& $slice[$index] == $byte)+
}
}
impl From<&[u8]> for Bom {
fn from(slice: &[u8]) -> Self {
if slice.len() >= 2 {
match slice[0] {
0 => {
if compare_slice!(slice, 4, [1 => 0, 2 => 0xfe, 3 => 0xff]) {
return Bom::Utf32Be;
}
}
0x0e => {
if compare_slice!(slice, 3, [1 => 0xfe, 2 => 0xff]) {
return Bom::Scsu;
}
}
0x2b => {
if compare_slice!(slice, 4, [1 => 0x2f, 2 => 0x76])
&& (slice[3] == 0x38
|| slice[3] == 0x39
|| slice[3] == 0x2b
|| slice[3] == 0x2f)
{
return Bom::Utf7;
}
}
0x84 => {
if compare_slice!(slice, 4, [1 => 0x31, 2 => 0x95, 3 => 0x33]) {
return Bom::Gb18030;
}
}
0xdd => {
if compare_slice!(slice, 4, [1 => 0x73, 2 => 0x66, 3 => 0x73]) {
return Bom::UtfEbcdic;
}
}
0xef => {
if compare_slice!(slice, 3, [1 => 0xbb, 2 => 0xbf]) {
return Bom::Utf8;
}
}
0xf7 => {
if compare_slice!(slice, 3, [1 => 0x64, 2 => 0x4c]) {
return Bom::Utf1;
}
}
0xfb => {
if compare_slice!(slice, 3, [1 => 0xee, 2 => 0x28]) {
return Bom::Bocu1;
}
}
0xfe => {
if slice[1] == 0xff {
return Bom::Utf16Be;
}
}
0xff => {
if slice[1] == 0xfe {
if compare_slice!(slice, 4, [2 => 0, 3 => 0]) {
return Bom::Utf32Le;
}
return Bom::Utf16Le;
}
}
_ => {}
}
}
Bom::Null
}
}
impl From<&mut File> for Bom {
fn from(file: &mut File) -> Self {
let mut data = [0u8; 4];
let mut result = file.read_exact(&mut data);
if let Err(ref error) = result {
if error.kind() == ErrorKind::UnexpectedEof {
let short_data = [0u8; 3];
result = file.read_exact(&mut data);
if let Err(ref error) = result {
if error.kind() == ErrorKind::UnexpectedEof {
let short_data = [0u8; 2];
result = file.read_exact(&mut data);
data[0] = short_data[0];
data[1] = short_data[1];
}
} else {
data[0] = short_data[0];
data[1] = short_data[1];
data[2] = short_data[2];
}
}
}
if result.is_ok() {
Bom::from(&data[0..])
} else {
Bom::Null
}
}
}
impl From<&str> for Bom {
fn from(path: &str) -> Self {
match File::open(path) {
Ok(mut file) => Bom::from(&mut file),
Err(_) => Bom::Null,
}
}
}
unsafe impl Send for Bom {}
unsafe impl Sync for Bom {}