mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-10 15:12:57 +08:00
Add export support
This commit is contained in:
106
src/utils/encoding.rs
Normal file
106
src/utils/encoding.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use crate::types::*;
|
||||
|
||||
pub fn decode_to_string(encoding: Encoding, data: &[u8]) -> Result<String, anyhow::Error> {
|
||||
match encoding {
|
||||
Encoding::Auto => decode_to_string(Encoding::Utf8, data)
|
||||
.or_else(|_| decode_to_string(Encoding::Cp932, data))
|
||||
.or_else(|_| decode_to_string(Encoding::Gb2312, data)),
|
||||
Encoding::Utf8 => Ok(String::from_utf8(data.to_vec())?),
|
||||
Encoding::Cp932 => {
|
||||
let result = encoding_rs::SHIFT_JIS.decode(data);
|
||||
if result.2 {
|
||||
Err(anyhow::anyhow!("Failed to decode Shift-JIS"))
|
||||
} else {
|
||||
Ok(result.0.to_string())
|
||||
}
|
||||
}
|
||||
Encoding::Gb2312 => {
|
||||
let result = encoding_rs::GBK.decode(data);
|
||||
if result.2 {
|
||||
Err(anyhow::anyhow!("Failed to decode GB2312"))
|
||||
} else {
|
||||
Ok(result.0.to_string())
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Encoding::CodePage(code_page) => {
|
||||
Ok(super::encoding_win::decode_to_string(code_page, data)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode_string(encoding: Encoding, data: &str) -> Result<Vec<u8>, anyhow::Error> {
|
||||
match encoding {
|
||||
Encoding::Auto => Ok(data.as_bytes().to_vec()),
|
||||
Encoding::Utf8 => Ok(data.as_bytes().to_vec()),
|
||||
Encoding::Cp932 => {
|
||||
let result = encoding_rs::SHIFT_JIS.encode(data);
|
||||
if result.2 {
|
||||
Err(anyhow::anyhow!("Failed to encode Shift-JIS"))
|
||||
} else {
|
||||
Ok(result.0.to_vec())
|
||||
}
|
||||
}
|
||||
Encoding::Gb2312 => {
|
||||
let result = encoding_rs::GBK.encode(data);
|
||||
if result.2 {
|
||||
Err(anyhow::anyhow!("Failed to encode GB2312"))
|
||||
} else {
|
||||
Ok(result.0.to_vec())
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Encoding::CodePage(code_page) => {
|
||||
Ok(super::encoding_win::encode_string(code_page, data)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_to_string() {
|
||||
assert_eq!(
|
||||
decode_to_string(
|
||||
Encoding::Utf8,
|
||||
&[228, 184, 173, 230, 150, 135, 230, 181, 139, 232, 175, 149]
|
||||
)
|
||||
.unwrap(),
|
||||
"中文测试".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
decode_to_string(
|
||||
Encoding::Cp932,
|
||||
&[
|
||||
130, 171, 130, 225, 130, 215, 130, 194, 130, 187, 130, 211, 130, 198
|
||||
]
|
||||
)
|
||||
.unwrap(),
|
||||
"きゃべつそふと".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
decode_to_string(Encoding::Gb2312, &[214, 208, 206, 196]).unwrap(),
|
||||
"中文".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
decode_to_string(
|
||||
Encoding::Auto,
|
||||
&[228, 184, 173, 230, 150, 135, 230, 181, 139, 232, 175, 149]
|
||||
)
|
||||
.unwrap(),
|
||||
"中文测试".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
decode_to_string(
|
||||
Encoding::Auto,
|
||||
&[
|
||||
130, 171, 130, 225, 130, 215, 130, 194, 130, 187, 130, 211, 130, 198
|
||||
]
|
||||
)
|
||||
.unwrap(),
|
||||
"きゃべつそふと".to_string()
|
||||
);
|
||||
#[cfg(windows)]
|
||||
assert_eq!(
|
||||
decode_to_string(Encoding::CodePage(936), &[214, 208, 206, 196]).unwrap(),
|
||||
"中文".to_string()
|
||||
);
|
||||
}
|
||||
121
src/utils/encoding_win.rs
Normal file
121
src/utils/encoding_win.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use windows_sys::Win32::Foundation::GetLastError;
|
||||
use windows_sys::Win32::Globalization::{MB_ERR_INVALID_CHARS, MultiByteToWideChar, WideCharToMultiByte};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WinError {
|
||||
pub code: u32,
|
||||
}
|
||||
|
||||
impl WinError {
|
||||
pub fn new(code: u32) -> Self {
|
||||
WinError { code }
|
||||
}
|
||||
|
||||
pub fn from_last_error() -> Self {
|
||||
let code = unsafe { GetLastError() };
|
||||
WinError::new(code)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for WinError {}
|
||||
|
||||
impl std::fmt::Display for WinError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Windows error code: {}", self.code)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode_to_string(cp: u32, data: &[u8]) -> Result<String, WinError> {
|
||||
let needed_len = unsafe {
|
||||
MultiByteToWideChar(
|
||||
cp,
|
||||
MB_ERR_INVALID_CHARS,
|
||||
data.as_ptr() as _,
|
||||
data.len() as i32,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
if needed_len == 0 {
|
||||
return Err(WinError::from_last_error());
|
||||
}
|
||||
let mut wc = Vec::with_capacity(needed_len as usize);
|
||||
wc.resize(needed_len as usize, 0);
|
||||
let result = unsafe {
|
||||
MultiByteToWideChar(
|
||||
cp,
|
||||
MB_ERR_INVALID_CHARS,
|
||||
data.as_ptr() as _,
|
||||
data.len() as i32,
|
||||
wc.as_mut_ptr(),
|
||||
needed_len,
|
||||
)
|
||||
};
|
||||
if result == 0 {
|
||||
return Err(WinError::from_last_error());
|
||||
}
|
||||
Ok(String::from_utf16_lossy(&wc))
|
||||
}
|
||||
|
||||
pub fn encode_string(cp: u32, data: &str) -> Result<Vec<u8>, WinError> {
|
||||
let wstr = data.encode_utf16().collect::<Vec<u16>>();
|
||||
let needed_len = unsafe {
|
||||
WideCharToMultiByte(
|
||||
cp,
|
||||
0,
|
||||
wstr.as_ptr(),
|
||||
wstr.len() as i32,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if needed_len == 0 {
|
||||
return Err(WinError::from_last_error());
|
||||
}
|
||||
let mut mb = Vec::with_capacity(needed_len as usize);
|
||||
mb.resize(needed_len as usize, 0);
|
||||
let result = unsafe {
|
||||
WideCharToMultiByte(
|
||||
cp,
|
||||
0,
|
||||
wstr.as_ptr(),
|
||||
wstr.len() as i32,
|
||||
mb.as_mut_ptr(),
|
||||
needed_len,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if result == 0 {
|
||||
return Err(WinError::from_last_error());
|
||||
}
|
||||
Ok(mb)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_to_string() {
|
||||
assert_eq!(
|
||||
decode_to_string(
|
||||
65001,
|
||||
&[228, 184, 173, 230, 150, 135, 230, 181, 139, 232, 175, 149]
|
||||
)
|
||||
.unwrap(),
|
||||
"中文测试".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
decode_to_string(
|
||||
932,
|
||||
&[
|
||||
130, 171, 130, 225, 130, 215, 130, 194, 130, 187, 130, 211, 130, 198
|
||||
]
|
||||
)
|
||||
.unwrap(),
|
||||
"きゃべつそふと".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
decode_to_string(936, &[214, 208, 206, 196]).unwrap(),
|
||||
"中文".to_string()
|
||||
);
|
||||
}
|
||||
67
src/utils/files.rs
Normal file
67
src/utils/files.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use crate::scripts::ALL_EXTS;
|
||||
|
||||
pub fn find_files(path: &String, recursive: bool) -> io::Result<Vec<String>> {
|
||||
let mut result = Vec::new();
|
||||
let dir_path = Path::new(&path);
|
||||
|
||||
if dir_path.is_dir() {
|
||||
for entry in fs::read_dir(dir_path)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_file()
|
||||
&& path.extension().map_or(true, |ext| {
|
||||
ALL_EXTS.contains(&ext.to_string_lossy().to_lowercase())
|
||||
})
|
||||
{
|
||||
if let Some(path_str) = path.to_str() {
|
||||
result.push(path_str.to_string());
|
||||
}
|
||||
} else if recursive && path.is_dir() {
|
||||
if let Some(path_str) = path.to_str() {
|
||||
let mut sub_files = find_files(&path_str.to_string(), recursive)?;
|
||||
result.append(&mut sub_files);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn collect_files(path: &String, recursive: bool) -> io::Result<(Vec<String>, bool)> {
|
||||
let pa = Path::new(path);
|
||||
if pa.is_dir() {
|
||||
return Ok((find_files(path, recursive)?, true));
|
||||
}
|
||||
if pa.is_file() {
|
||||
return Ok((vec![path.clone()], false));
|
||||
}
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("Path {} is neither a file nor a directory", pa.display()),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn read_file<F: AsRef<Path> + ?Sized>(f: &F) -> io::Result<Vec<u8>> {
|
||||
let mut content = Vec::new();
|
||||
if f.as_ref() == Path::new("-") {
|
||||
io::stdin().read_to_end(&mut content)?;
|
||||
} else {
|
||||
content = fs::read(f)?;
|
||||
}
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
pub fn write_file<F: AsRef<Path> + ?Sized>(f: &F) -> io::Result<Box<dyn Write>> {
|
||||
Ok(if f.as_ref() == Path::new("-") {
|
||||
Box::new(io::stdout())
|
||||
} else {
|
||||
Box::new(fs::File::create(f)?)
|
||||
})
|
||||
}
|
||||
4
src/utils/mod.rs
Normal file
4
src/utils/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod encoding;
|
||||
#[cfg(windows)]
|
||||
mod encoding_win;
|
||||
pub mod files;
|
||||
Reference in New Issue
Block a user