最后,实现命令行工具pngme的四种操作:对 PNG 文件进行信息编码、解码其中存储的信息、移除特定类型的数据块以及打印 PNG 文件中所有数据块的信息。
详细实现分析
1. 命令行参数定义
#![allow(unused_variables)]
use clap::Parser;
use std::path::PathBuf;
use anyhow::Result;
use std::str::FromStr;
use pngme::png::Png; // 修改为使用库名引用
use pngme::chunk::Chunk; // 修改为使用库名引用
use pngme::chunk_type::ChunkType; // 修改为使用库名引用
use std::fs::File;
use std::io::{Read, Write};
// 定义命令行参数的枚举和结构体
pub enum PngMeArgs {
Encode(EncodeArgs),
Decode(DecodeArgs),
Remove(RemoveArgs),
Print(PrintArgs),
}
#[derive(clap::Parser, Debug)]
pub struct EncodeArgs {
/// 输入的 PNG 文件路径
// #[clap(parse(from_os_str))]
#[clap(value_parser)]
pub file_path: PathBuf,
/// 数据块类型
pub chunk_type: String,
/// 要编码的消息
pub message: String,
/// 可选的输出文件路径
#[clap(short = 'o', long = "output", value_parser)]
pub output: Option<PathBuf>,
}
#[derive(clap::Parser, Debug)]
pub struct DecodeArgs {
/// 输入的 PNG 文件路径
// #[clap(parse(from_os_str))]
pub file_path: PathBuf,
/// 数据块类型
pub chunk_type: String,
}
#[derive(clap::Parser, Debug)]
pub struct RemoveArgs {
/// 输入的 PNG 文件路径
// #[clap(parse(from_os_str))]
pub file_path: PathBuf,
/// 数据块类型
pub chunk_type: String,
}
#[derive(clap::Parser, Debug)]
pub struct PrintArgs {
/// 输入的 PNG 文件路径
// #[clap(parse(from_os_str))]
pub file_path: PathBuf,
}
impl PngMeArgs {
// 解析命令行参数的方法
pub fn from_args() -> Self {
let args = Cli::parse();
match args.command {
Commands::Encode(args) => PngMeArgs::Encode(args),
Commands::Decode(args) => PngMeArgs::Decode(args),
Commands::Remove(args) => PngMeArgs::Remove(args),
Commands::Print(args) => PngMeArgs::Print(args),
}
}
}
- 定义一个枚举
PngMeArgs,它包含四个变体,分别对应四种操作:Encode、Decode、Remove和Print。每个变体关联一个结构体,这些结构体使用clap库的Parser派生宏来定义各自所需的命令行参数。 EncodeArgs:需要输入 PNG 文件路径、数据块类型、要编码的消息,还可以指定可选的输出文件路径。DecodeArgs:需要输入 PNG 文件路径和要解码的数据块类型。 -RemoveArgs:需要输入 PNG 文件路径和要移除的数据块类型。PrintArgs:仅需要输入 PNG 文件路径。PngMeArgs实现from_args方法,用于从命令行解析参数并返回相应的PngMeArgs枚举实例。
2. 命令行接口结构体与枚举
// 命令行接口结构体
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Cli {
#[clap(subcommand)]
command: Commands,
}
// 命令枚举
#[derive(Parser, Debug)]
enum Commands {
Encode(EncodeArgs),
Decode(DecodeArgs),
Remove(RemoveArgs),
Print(PrintArgs),
}
定义 Cli 结构体和 Commands 枚举,它们同样使用 clap 库的 Parser 派生宏。Cli 结构体包含一个 Commands 枚举类型的字段,用于区分不同的命令。Commands 枚举的每个变体关联相应的参数结构体。
3. 主函数实现
fn main() -> Result<()> {
let args = PngMeArgs::from_args();
match args {
PngMeArgs::Encode(args) => {
let file_path_clone = args.file_path.clone();
let mut file = File::open(file_path_clone)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
let mut png = Png::try_from(buffer.as_slice())?;
let chunk_type = ChunkType::from_str(&args.chunk_type)?;
let chunk = Chunk::new(chunk_type, args.message.into_bytes());
png.append_chunk(chunk);
let output_path = args.output.unwrap_or_else(|| args.file_path.clone());
let mut output_file = File::create(output_path)?;
output_file.write_all(&png.as_bytes())?;
println!("Message encoded successfully.");
}
PngMeArgs::Decode(args) => {
let mut file = File::open(args.file_path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
let png = Png::try_from(buffer.as_slice())?;
if let Some(chunk) = png.chunk_by_type(&args.chunk_type) {
if let Ok(message) = chunk.data_as_string() {
println!("Decoded message: {}", message);
} else {
println!("Failed to decode message as valid UTF-8.");
}
} else {
println!("Chunk of type {} not found.", args.chunk_type);
}
}
PngMeArgs::Remove(args) => {
let mut file = File::open(args.file_path.clone())?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
let mut png = Png::try_from(buffer.as_slice())?;
if let Ok(removed_chunk) = png.remove_first_chunk(&args.chunk_type) {
let mut output_file = File::create(args.file_path)?;
output_file.write_all(&png.as_bytes())?;
println!("Chunk of type {} removed successfully.", args.chunk_type);
} else {
println!("Chunk of type {} not found.", args.chunk_type);
}
}
PngMeArgs::Print(args) => {
let mut file = File::open(args.file_path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
let png = Png::try_from(buffer.as_slice())?;
for chunk in png.chunks() {
println!("{}", chunk);
}
}
}
Ok(())
}
主函数 main 根据解析得到的 PngMeArgs 枚举实例执行不同的操作:
- 编码操作(
PngMeArgs::Encode) - 打开输入的 PNG 文件,将其内容读取到缓冲区。 - 从缓冲区内容创建Png对象。 - 将输入的数据块类型字符串转换为
ChunkType对象,再结合要编码的消息创建Chunk对象。 - 将新创建的
Chunk对象添加到Png对象中。 - 如果指定了输出文件路径,则将修改后的Png对象写入该文件;否则覆盖原文件。最后输出编码成功的提示信息。 - 解码操作(
PngMeArgs::Decode) - 打开输入的 PNG 文件,读取内容到缓冲区并创建Png对象。 - 查找指定类型的数据块,如果找到则尝试将其数据解码为 UTF - 8 字符串并输出;若解码失败或未找到该类型的数据块,则输出相应的错误信息。 - 移除操作(
PngMeArgs::Remove) - 打开输入的 PNG 文件,读取内容到缓冲区并创建Png对象。 - 尝试移除第一个指定类型的数据块,如果移除成功,则将修改后的Png对象写回原文件并输出移除成功的提示信息;若未找到该类型的数据块,则输出相应的提示。 - 打印操作(
PngMeArgs::Print) - 打开输入的 PNG 文件,读取内容到缓冲区并创建Png对象。 - 遍历
Png对象中的所有数据块,并将每个数据块的信息打印输出。
总结
通过上述代码实现,我们可以方便地在命令行中对 PNG 文件进行信息的编码、解码、移除和查看操作。