Skip to main content

exiftool_rs_wrapper/
repl.rs

1//! REPL 交互式命令行工具
2//!
3//! 提供交互式的 ExifTool shell
4
5use crate::error::Result;
6use crate::ExifTool;
7use std::io::{self, Write};
8
9/// REPL 交互式 shell
10pub struct ReplShell {
11    exiftool: ExifTool,
12    verbose: bool,
13    show_help_on_start: bool,
14}
15
16impl ReplShell {
17    /// 创建新的 REPL shell
18    pub fn new(exiftool: ExifTool) -> Self {
19        Self {
20            exiftool,
21            verbose: false,
22            show_help_on_start: true,
23        }
24    }
25
26    /// 启用详细模式
27    pub fn verbose(mut self, yes: bool) -> Self {
28        self.verbose = yes;
29        self
30    }
31
32    /// 启动时显示帮助
33    pub fn show_help_on_start(mut self, yes: bool) -> Self {
34        self.show_help_on_start = yes;
35        self
36    }
37
38    /// 运行 REPL 循环
39    pub fn run(&self) -> Result<()> {
40        let stdin = io::stdin();
41        let mut stdout = io::stdout();
42
43        if self.show_help_on_start {
44            self.print_help(&mut stdout)?;
45        }
46
47        writeln!(stdout, "ExifTool REPL v{}", env!("CARGO_PKG_VERSION"))?;
48        writeln!(stdout, "输入 'help' 查看帮助,输入 'quit' 退出。\n")?;
49
50        loop {
51            write!(stdout, "exiftool> ")?;
52            stdout.flush()?;
53
54            let mut line = String::new();
55            if stdin.read_line(&mut line).is_err() {
56                break;
57            }
58
59            let input = line.trim();
60            if input.is_empty() {
61                continue;
62            }
63
64            match self.process_command(input) {
65                Ok(should_quit) => {
66                    if should_quit {
67                        writeln!(stdout, "再见!")?;
68                        break;
69                    }
70                }
71                Err(e) => {
72                    writeln!(stdout, "错误: {}", e)?;
73                }
74            }
75        }
76
77        Ok(())
78    }
79
80    /// 处理单个命令
81    fn process_command(&self, input: &str) -> Result<bool> {
82        let parts: Vec<&str> = input.split_whitespace().collect();
83        if parts.is_empty() {
84            return Ok(false);
85        }
86
87        let cmd = parts[0].to_lowercase();
88        let args = &parts[1..];
89
90        match cmd.as_str() {
91            "quit" | "exit" | "q" => Ok(true),
92            "help" | "h" => {
93                self.print_help(&mut io::stdout())?;
94                Ok(false)
95            }
96            "read" | "r" => {
97                if args.is_empty() {
98                    println!("用法: read <文件路径> [标签名]");
99                } else {
100                    self.cmd_read(args)?;
101                }
102                Ok(false)
103            }
104            "write" | "w" => {
105                if args.len() < 3 {
106                    println!("用法: write <文件路径> <标签名> <值>");
107                } else {
108                    self.cmd_write(args)?;
109                }
110                Ok(false)
111            }
112            "delete" | "d" => {
113                if args.len() < 2 {
114                    println!("用法: delete <文件路径> <标签名>");
115                } else {
116                    self.cmd_delete(args)?;
117                }
118                Ok(false)
119            }
120            "batch" | "b" => {
121                if args.is_empty() {
122                    println!("用法: batch <文件路径1> [<文件路径2> ...]");
123                } else {
124                    self.cmd_batch(args)?;
125                }
126                Ok(false)
127            }
128            "tags" | "t" => {
129                self.cmd_list_tags()?;
130                Ok(false)
131            }
132            "verbose" | "v" => {
133                println!("详细模式: {}", self.verbose);
134                Ok(false)
135            }
136            "version" => {
137                match self.exiftool.version() {
138                    Ok(ver) => println!("ExifTool 版本: {}", ver),
139                    Err(e) => println!("无法获取版本: {}", e),
140                }
141                Ok(false)
142            }
143            _ => {
144                println!("未知命令: {}", cmd);
145                println!("输入 'help' 查看可用命令");
146                Ok(false)
147            }
148        }
149    }
150
151    /// read 命令
152    fn cmd_read(&self, args: &[&str]) -> Result<()> {
153        let path = args[0];
154
155        if args.len() > 1 {
156            // 读取特定标签
157            let tag_name = args[1];
158            match self.exiftool.read_tag::<String, _, _>(path, tag_name) {
159                Ok(value) => println!("{}: {}", tag_name, value),
160                Err(e) => println!("读取标签失败: {}", e),
161            }
162        } else {
163            // 读取所有元数据
164            match self.exiftool.query(path).execute() {
165                Ok(metadata) => {
166                    println!("文件: {}", path);
167                    for (key, value) in metadata.iter() {
168                        println!("  {}: {}", key, value);
169                    }
170                }
171                Err(e) => println!("读取元数据失败: {}", e),
172            }
173        }
174
175        Ok(())
176    }
177
178    /// write 命令
179    fn cmd_write(&self, args: &[&str]) -> Result<()> {
180        let path = args[0];
181        let tag = args[1];
182        let value = args[2..].join(" ");
183
184        match self
185            .exiftool
186            .write(path)
187            .tag(tag, &value)
188            .overwrite_original(true)
189            .execute()
190        {
191            Ok(_) => println!("写入成功: {} = {}", tag, value),
192            Err(e) => println!("写入失败: {}", e),
193        }
194
195        Ok(())
196    }
197
198    /// delete 命令
199    fn cmd_delete(&self, args: &[&str]) -> Result<()> {
200        let path = args[0];
201        let tag = args[1];
202
203        match self
204            .exiftool
205            .write(path)
206            .delete(tag)
207            .overwrite_original(true)
208            .execute()
209        {
210            Ok(_) => println!("删除标签成功: {}", tag),
211            Err(e) => println!("删除标签失败: {}", e),
212        }
213
214        Ok(())
215    }
216
217    /// batch 命令
218    fn cmd_batch(&self, args: &[&str]) -> Result<()> {
219        let paths: Vec<&str> = args.to_vec();
220
221        match self.exiftool.query_batch(&paths).execute() {
222            Ok(results) => {
223                for (path, metadata) in results {
224                    println!("\n文件: {}", path.display());
225                    if self.verbose {
226                        for (key, value) in metadata.iter() {
227                            println!("  {}: {}", key, value);
228                        }
229                    } else {
230                        // 只显示关键信息
231                        if let Some(make) = metadata.get("Make") {
232                            println!("  制造商: {}", make);
233                        }
234                        if let Some(model) = metadata.get("Model") {
235                            println!("  型号: {}", model);
236                        }
237                        if let Some(date) = metadata.get("DateTimeOriginal") {
238                            println!("  拍摄时间: {}", date);
239                        }
240                    }
241                }
242            }
243            Err(e) => println!("批量读取失败: {}", e),
244        }
245
246        Ok(())
247    }
248
249    /// tags 命令
250    fn cmd_list_tags(&self) -> Result<()> {
251        println!("常用标签:");
252        println!("  EXIF: Make, Model, DateTimeOriginal, ImageWidth, ImageHeight");
253        println!("  IPTC: Keywords, Caption-Abstract, City, Country");
254        println!("  XMP:  Title, Creator, Description, Rights");
255        println!("  GPS:  GPSLatitude, GPSLongitude, GPSAltitude");
256        println!("\n使用 'read <文件> <标签名>' 读取特定标签");
257        Ok(())
258    }
259
260    /// 打印帮助信息
261    fn print_help(&self, writer: &mut dyn Write) -> io::Result<()> {
262        writeln!(writer, "\n=== ExifTool REPL 帮助 ===\n")?;
263        writeln!(writer, "命令列表:")?;
264        writeln!(writer, "  read, r    <路径> [标签]    读取文件元数据")?;
265        writeln!(writer, "  write, w   <路径> <标签> <值> 写入标签")?;
266        writeln!(writer, "  delete, d  <路径> <标签>    删除标签")?;
267        writeln!(writer, "  batch, b   <路径1> ...      批量处理")?;
268        writeln!(writer, "  tags, t                   列出常用标签")?;
269        writeln!(writer, "  verbose, v                切换详细模式")?;
270        writeln!(writer, "  version                   显示版本")?;
271        writeln!(writer, "  help, h                   显示帮助")?;
272        writeln!(writer, "  quit, exit, q             退出\n")?;
273        Ok(())
274    }
275}
276
277/// 启动 REPL shell 的便捷函数
278pub fn run_repl(exiftool: ExifTool) -> Result<()> {
279    let shell = ReplShell::new(exiftool);
280    shell.run()
281}