04-cli-tool.md 7.5 KB

实战案例:命令行工具

难度:⭐⭐ 中等 | 预计时间: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)

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)

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)

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)

[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"

🚀 使用示例

# 安装
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

✅ 验收清单

  • 命令行帮助信息正确显示
  • 前缀/后缀功能正常
  • 替换功能正常
  • 序号命名功能正常
  • 预览模式不执行实际操作
  • 扩展名过滤正常
  • 递归处理正常

💡 进阶挑战

  • 添加撤销功能(记录操作日志)
  • 支持正则表达式
  • 添加日期格式化规则
  • 支持配置文件
  • 添加交互式模式