vibe-coding-cn/i18n/zh/documents/实战案例/04-cli-tool.md

299 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 实战案例:命令行工具
> 难度:⭐⭐ 中等 | 预计时间1-2 小时 | 技术栈Python + Click
## 🎯 项目目标
构建一个实用的命令行工具,实现:
- 文件批量重命名
- 支持多种命名规则
- 预览和确认机制
- 彩色输出
## 📋 开始前的准备
### 环境要求
- Python 3.8+
- pip
### 第一步:需求澄清
复制以下提示词给 AI
```
我想用 Vibe Coding 的方式开发一个文件批量重命名的命令行工具。
技术要求:
- 语言Python 3.8+
- CLI 框架Click
- 输出美化Rich
功能需求:
1. 支持多种重命名规则:
- 添加前缀/后缀
- 替换文本
- 序号命名
- 日期命名
2. 预览模式(不实际执行)
3. 递归处理子目录(可选)
4. 支持文件类型过滤
请帮我:
1. 确认技术栈是否合适
2. 生成项目结构
3. 一步步指导我完成开发
要求:每完成一步问我是否成功,再继续下一步。
```
## 🏗️ 项目结构
```
file-renamer/
├── renamer/
│ ├── __init__.py
│ ├── cli.py # CLI 入口
│ ├── core.py # 核心逻辑
│ └── rules.py # 重命名规则
├── tests/
│ └── test_core.py
├── pyproject.toml
└── README.md
```
## 🔧 核心代码
### CLI 入口 (renamer/cli.py)
```python
import click
from rich.console import Console
from rich.table import Table
from .core import FileRenamer
from .rules import PrefixRule, SuffixRule, ReplaceRule, SequenceRule
console = Console()
@click.group()
@click.version_option(version='1.0.0')
def cli():
"""文件批量重命名工具"""
pass
@cli.command()
@click.argument('directory', type=click.Path(exists=True))
@click.option('--prefix', '-p', help='添加前缀')
@click.option('--suffix', '-s', help='添加后缀')
@click.option('--replace', '-r', nargs=2, help='替换文本 (旧文本 新文本)')
@click.option('--sequence', '-n', is_flag=True, help='序号命名')
@click.option('--ext', '-e', multiple=True, help='文件扩展名过滤')
@click.option('--recursive', '-R', is_flag=True, help='递归处理子目录')
@click.option('--dry-run', '-d', is_flag=True, help='预览模式')
def rename(directory, prefix, suffix, replace, sequence, ext, recursive, dry_run):
"""批量重命名文件"""
renamer = FileRenamer(directory, recursive=recursive, extensions=ext)
# 添加规则
if prefix:
renamer.add_rule(PrefixRule(prefix))
if suffix:
renamer.add_rule(SuffixRule(suffix))
if replace:
renamer.add_rule(ReplaceRule(replace[0], replace[1]))
if sequence:
renamer.add_rule(SequenceRule())
if not renamer.rules:
console.print("[red]错误:请至少指定一个重命名规则[/red]")
return
# 获取预览
changes = renamer.preview()
if not changes:
console.print("[yellow]没有找到匹配的文件[/yellow]")
return
# 显示预览表格
table = Table(title="重命名预览")
table.add_column("原文件名", style="cyan")
table.add_column("新文件名", style="green")
for old, new in changes:
table.add_row(old, new)
console.print(table)
console.print(f"\n共 [bold]{len(changes)}[/bold] 个文件")
if dry_run:
console.print("[yellow]预览模式,未执行实际操作[/yellow]")
return
# 确认执行
if click.confirm('确认执行重命名?'):
renamer.execute()
console.print("[green]✓ 重命名完成![/green]")
else:
console.print("[yellow]已取消[/yellow]")
if __name__ == '__main__':
cli()
```
### 核心逻辑 (renamer/core.py)
```python
import os
from pathlib import Path
from typing import List, Tuple
class FileRenamer:
def __init__(self, directory: str, recursive: bool = False, extensions: tuple = ()):
self.directory = Path(directory)
self.recursive = recursive
self.extensions = extensions
self.rules = []
def add_rule(self, rule):
self.rules.append(rule)
def get_files(self) -> List[Path]:
pattern = '**/*' if self.recursive else '*'
files = []
for path in self.directory.glob(pattern):
if path.is_file():
if self.extensions:
if path.suffix.lower() in [f'.{e.lower()}' for e in self.extensions]:
files.append(path)
else:
files.append(path)
return sorted(files)
def apply_rules(self, filename: str) -> str:
result = filename
for rule in self.rules:
result = rule.apply(result)
return result
def preview(self) -> List[Tuple[str, str]]:
changes = []
for file in self.get_files():
old_name = file.stem
new_name = self.apply_rules(old_name)
if old_name != new_name:
changes.append((file.name, new_name + file.suffix))
return changes
def execute(self):
for file in self.get_files():
old_name = file.stem
new_name = self.apply_rules(old_name)
if old_name != new_name:
new_path = file.parent / (new_name + file.suffix)
file.rename(new_path)
```
### 重命名规则 (renamer/rules.py)
```python
from abc import ABC, abstractmethod
class Rule(ABC):
@abstractmethod
def apply(self, filename: str) -> str:
pass
class PrefixRule(Rule):
def __init__(self, prefix: str):
self.prefix = prefix
def apply(self, filename: str) -> str:
return f"{self.prefix}{filename}"
class SuffixRule(Rule):
def __init__(self, suffix: str):
self.suffix = suffix
def apply(self, filename: str) -> str:
return f"{filename}{self.suffix}"
class ReplaceRule(Rule):
def __init__(self, old: str, new: str):
self.old = old
self.new = new
def apply(self, filename: str) -> str:
return filename.replace(self.old, self.new)
class SequenceRule(Rule):
def __init__(self, start: int = 1, padding: int = 3):
self.counter = start
self.padding = padding
def apply(self, filename: str) -> str:
result = f"{str(self.counter).zfill(self.padding)}_{filename}"
self.counter += 1
return result
```
## 📦 安装配置 (pyproject.toml)
```toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "file-renamer"
version = "1.0.0"
description = "文件批量重命名工具"
requires-python = ">=3.8"
dependencies = [
"click>=8.0",
"rich>=13.0",
]
[project.scripts]
renamer = "renamer.cli:cli"
```
## 🚀 使用示例
```bash
# 安装
pip install -e .
# 添加前缀
renamer rename ./photos --prefix "2024_"
# 替换文本
renamer rename ./docs --replace "old" "new"
# 序号命名 + 过滤扩展名
renamer rename ./images --sequence --ext jpg --ext png
# 预览模式
renamer rename ./files --prefix "backup_" --dry-run
# 递归处理
renamer rename ./project --suffix "_v2" --recursive
```
## ✅ 验收清单
- [ ] 命令行帮助信息正确显示
- [ ] 前缀/后缀功能正常
- [ ] 替换功能正常
- [ ] 序号命名功能正常
- [ ] 预览模式不执行实际操作
- [ ] 扩展名过滤正常
- [ ] 递归处理正常
## 💡 进阶挑战
- 添加撤销功能(记录操作日志)
- 支持正则表达式
- 添加日期格式化规则
- 支持配置文件
- 添加交互式模式