写一个 Rust 小应用 pngme - 4.实现具体操作

78 阅读4分钟

最后,实现命令行工具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,它包含四个变体,分别对应四种操作:EncodeDecodeRemovePrint。每个变体关联一个结构体,这些结构体使用 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 文件进行信息的编码、解码、移除和查看操作。