diff --git a/README.md b/README.md index 9770379..1ca5f46 100644 --- a/README.md +++ b/README.md @@ -1 +1,204 @@ -# devkit-cli \ No newline at end of file +# devkit-cli + +开发者工具集命令行工具 - 系统信息、文件重命名、任务管理 + +--- + +## 🚀 安装和运行 + +### 环境要求 +- Python 3.8+ +- Windows / macOS / Linux + +### 安装依赖 + +```bash +# 克隆或下载项目后,进入项目目录 +cd devkit-cli + +# 创建虚拟环境(推荐) +python -m venv .venv + +# 激活虚拟环境 +# Windows (PowerShell): +.venv\Scripts\Activate.ps1 + +# Windows (CMD): +# .venv\Scripts\activate.bat + +# macOS/Linux: +# source .venv/bin/activate + +# 安装依赖 +pip install typer rich psutil + +# 以开发模式安装CLI工具 +pip install -e . +``` + +### 运行方式 + +**方式一:使用命令行工具(推荐)** +```bash +# 激活虚拟环境后可直接使用 +devkit-cli --help +devkit --help # 短名命令也可用 +``` + +**方式二:直接运行Python脚本** +```bash +python devkit.py --help +``` + +--- + +## 📖 功能使用说明 + +### 1. 📊 系统信息 - info 子命令 + +查看本机系统信息(CPU、内存、磁盘、系统版本),以彩色表格展示。 + +**使用方法:** +```bash +devkit-cli info show +``` + +**包含内容:** +- 基本信息(系统版本、架构、主机名) +- CPU信息(物理/逻辑核心数、频率、各核心使用率) +- 内存信息(物理内存、交换内存的总量和使用率) +- 磁盘信息(各分区的文件系统、容量和使用情况) +- 网络接口(IP地址、MAC地址) + +--- + +### 2. 📁 文件重命名 - file 子命令 + +批量文件重命名,支持自定义前缀、自动编号、扩展名过滤。 + +**常用命令:** + +```bash +# 列出目录中的文件 +devkit-cli file list # 当前目录 +devkit-cli file list ./photos # 指定目录 +devkit-cli file list --ext .jpg # 只显示jpg文件 +devkit-cli file list -e .jpg -e .png # 支持多个扩展名 + +# 预览重命名(不实际执行) +devkit-cli file rename --prefix vacation_ --dry-run + +# 实际执行重命名 +devkit-cli file rename --prefix img_ --digits 4 +devkit-cli file rename --prefix doc_ --start 100 + +# 指定目录和扩展名 +devkit-cli file rename ./photos --prefix summer_ --ext .jpg + +# 跳过确认提示 +devkit-cli file rename --prefix test_ --yes +``` + +**参数说明:** +| 参数 | 短名 | 说明 | 默认值 | +|------|------|------|--------| +| `--prefix` | `-p` | 文件名前缀(必填) | - | +| `--start` | `-s` | 起始编号 | 1 | +| `--digits` | `-d` | 编号位数(如 3=001) | 3 | +| `--ext` | `-e` | 指定文件扩展名(可多次使用) | 所有文件 | +| `--dry-run` | `-n` | 预览模式,不实际执行 | False | +| `--yes` | `-y` | 跳过确认提示 | False | + +--- + +### 3. ✅ 任务管理 - todo 子命令 + +本地任务管理,支持添加、查看、标记完成、删除。 + +**任务操作:** + +```bash +# 添加任务 +devkit-cli todo add "完成项目文档" +devkit-cli todo add "代码审查" --desc "审查PR #123" --priority high + +# 查看任务列表 +devkit-cli todo list # 查看未完成任务 +devkit-cli todo list --all # 查看所有任务(包括已完成) +devkit-cli todo list --priority high # 按优先级过滤 + +# 查看单个任务详情 +devkit-cli todo show 1 + +# 标记任务状态 +devkit-cli todo done 1 # 标记完成 +devkit-cli todo undone 1 # 标记未完成 + +# 删除任务 +devkit-cli todo delete 1 # 删除(会确认) +devkit-cli todo delete 1 --yes # 删除(跳过确认) + +# 清除所有已完成任务 +devkit-cli todo clear +``` + +**优先级选项:** +- `high` - 高优先级(🔴) +- `medium` - 中优先级(🟡,默认) +- `low` - 低优先级(🟢) + +**数据存储:** +- 任务数据保存在用户目录下的 `.devkit_todo.json` +- 可直接编辑或备份该文件 + +--- + +## 💡 通用功能 + +### 版本信息 +```bash +devkit-cli --version +devkit-cli -v +``` + +### 帮助信息 +```bash +devkit-cli --help +devkit-cli info --help +devkit-cli file --help +devkit-cli todo --help +``` + +--- + +## 🌟 特性亮点 + +- **彩色美化输出:** 使用 Rich 库提供精美的表格和彩色输出 +- **参数校验:** 自动校验输入参数,提供友好错误提示 +- **进度显示:** 文件重命名时显示实时进度条 +- **交互式确认:** 危险操作前提供确认提示 +- **零配置:** 开箱即用,无需额外配置文件 +- **跨平台:** 支持 Windows、macOS、Linux + +--- + +## 📦 项目结构 + +``` +devkit-cli/ +├── devkit_cli/ +│ ├── __init__.py # 包初始化 +│ ├── cli.py # 主入口和命令调度 +│ ├── info.py # 系统信息模块 +│ ├── file_rename.py # 文件重命名模块 +│ └── todo.py # 任务管理模块 +├── devkit.py # 直接运行入口 +├── pyproject.toml # 项目配置 +└── README.md # 使用说明 +``` + +--- + +## 📝 许可证 + +MIT diff --git a/devkit.py b/devkit.py new file mode 100644 index 0000000..870bcd6 --- /dev/null +++ b/devkit.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 +""" +devkit-cli 入口文件 +可以直接运行此文件:python devkit.py +""" + +from devkit_cli.cli import app + +if __name__ == "__main__": + app() diff --git a/devkit_cli.egg-info/PKG-INFO b/devkit_cli.egg-info/PKG-INFO new file mode 100644 index 0000000..143eef2 --- /dev/null +++ b/devkit_cli.egg-info/PKG-INFO @@ -0,0 +1,27 @@ +Metadata-Version: 2.4 +Name: devkit-cli +Version: 1.0.0 +Summary: 开发者工具集命令行工具 - 系统信息、文件重命名、任务管理 +Author-email: Developer +Project-URL: Homepage, https://github.com/example/devkit-cli +Project-URL: Bug Tracker, https://github.com/example/devkit-cli/issues +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Operating System :: OS Independent +Classifier: Topic :: Utilities +Classifier: Topic :: System :: Shells +Classifier: Intended Audience :: Developers +Classifier: Environment :: Console +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +Requires-Dist: typer>=0.9.0 +Requires-Dist: rich>=13.0.0 +Requires-Dist: psutil>=5.9.0 + +# devkit-cli diff --git a/devkit_cli.egg-info/SOURCES.txt b/devkit_cli.egg-info/SOURCES.txt new file mode 100644 index 0000000..0dbe489 --- /dev/null +++ b/devkit_cli.egg-info/SOURCES.txt @@ -0,0 +1,13 @@ +README.md +pyproject.toml +devkit_cli/__init__.py +devkit_cli/cli.py +devkit_cli/file_rename.py +devkit_cli/info.py +devkit_cli/todo.py +devkit_cli.egg-info/PKG-INFO +devkit_cli.egg-info/SOURCES.txt +devkit_cli.egg-info/dependency_links.txt +devkit_cli.egg-info/entry_points.txt +devkit_cli.egg-info/requires.txt +devkit_cli.egg-info/top_level.txt \ No newline at end of file diff --git a/devkit_cli.egg-info/dependency_links.txt b/devkit_cli.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/devkit_cli.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/devkit_cli.egg-info/entry_points.txt b/devkit_cli.egg-info/entry_points.txt new file mode 100644 index 0000000..73b9ce9 --- /dev/null +++ b/devkit_cli.egg-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +devkit = devkit_cli.cli:app +devkit-cli = devkit_cli.cli:app diff --git a/devkit_cli.egg-info/requires.txt b/devkit_cli.egg-info/requires.txt new file mode 100644 index 0000000..88182e2 --- /dev/null +++ b/devkit_cli.egg-info/requires.txt @@ -0,0 +1,3 @@ +typer>=0.9.0 +rich>=13.0.0 +psutil>=5.9.0 diff --git a/devkit_cli.egg-info/top_level.txt b/devkit_cli.egg-info/top_level.txt new file mode 100644 index 0000000..59e8581 --- /dev/null +++ b/devkit_cli.egg-info/top_level.txt @@ -0,0 +1 @@ +devkit_cli diff --git a/devkit_cli/__init__.py b/devkit_cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/devkit_cli/__pycache__/__init__.cpython-314.pyc b/devkit_cli/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..0636987 Binary files /dev/null and b/devkit_cli/__pycache__/__init__.cpython-314.pyc differ diff --git a/devkit_cli/__pycache__/cli.cpython-314.pyc b/devkit_cli/__pycache__/cli.cpython-314.pyc new file mode 100644 index 0000000..a9e98cd Binary files /dev/null and b/devkit_cli/__pycache__/cli.cpython-314.pyc differ diff --git a/devkit_cli/__pycache__/file_rename.cpython-314.pyc b/devkit_cli/__pycache__/file_rename.cpython-314.pyc new file mode 100644 index 0000000..e90cd0a Binary files /dev/null and b/devkit_cli/__pycache__/file_rename.cpython-314.pyc differ diff --git a/devkit_cli/__pycache__/info.cpython-314.pyc b/devkit_cli/__pycache__/info.cpython-314.pyc new file mode 100644 index 0000000..3ac24e8 Binary files /dev/null and b/devkit_cli/__pycache__/info.cpython-314.pyc differ diff --git a/devkit_cli/__pycache__/todo.cpython-314.pyc b/devkit_cli/__pycache__/todo.cpython-314.pyc new file mode 100644 index 0000000..7374a3e Binary files /dev/null and b/devkit_cli/__pycache__/todo.cpython-314.pyc differ diff --git a/devkit_cli/cli.py b/devkit_cli/cli.py new file mode 100644 index 0000000..3bec28d --- /dev/null +++ b/devkit_cli/cli.py @@ -0,0 +1,66 @@ +""" +devkit-cli - 开发者工具集命令行工具 + +功能模块: +1. info - 查看本机系统信息 +2. file - 批量文件重命名 +3. todo - 本地任务管理 +""" + +import typer +from rich.console import Console +from typing import Optional + +# 初始化控制台 +console = Console() +app = typer.Typer( + name="devkit-cli", + help="开发者工具集命令行工具", + add_completion=False, + rich_markup_mode="rich", +) + +# 导入子命令模块 +from .info import info_app +from .file_rename import file_app +from .todo import todo_app + +# 注册子命令 +app.add_typer(info_app, name="info", help="📊 查看本机系统信息") +app.add_typer(file_app, name="file", help="📁 批量文件重命名") +app.add_typer(todo_app, name="todo", help="✅ 本地任务管理") + +# 版本信息 +__version__ = "1.0.0" + +def version_callback(value: bool) -> None: + """显示版本信息""" + if value: + console.print(f"[bold green]devkit-cli[/bold green] version: [cyan]{__version__}[/cyan]") + raise typer.Exit() + +@app.callback(invoke_without_command=True) +def main( + ctx: typer.Context, + version: Optional[bool] = typer.Option( + None, "--version", "-v", callback=version_callback, is_eager=True, help="显示版本信息" + ), +) -> None: + """ + [bold green]devkit-cli[/bold green] - 开发者工具集命令行工具 + + 使用示例: + $ devkit-cli info + $ devkit-cli file rename --prefix test_ + $ devkit-cli todo add "完成项目文档" + """ + if ctx.invoked_subcommand is None: + console.print("[bold yellow]⚠️ 请指定一个子命令[/bold yellow]") + console.print("使用 --help 查看帮助信息") + console.print("\n可用子命令:") + console.print(" [cyan]info[/cyan] 📊 查看本机系统信息") + console.print(" [cyan]file[/cyan] 📁 批量文件重命名") + console.print(" [cyan]todo[/cyan] ✅ 本地任务管理") + +if __name__ == "__main__": + app() diff --git a/devkit_cli/file_rename.py b/devkit_cli/file_rename.py new file mode 100644 index 0000000..f589c0e --- /dev/null +++ b/devkit_cli/file_rename.py @@ -0,0 +1,222 @@ +""" +文件操作模块 - 批量文件重命名,支持自定义前缀、自动编号 +""" + +import os +import typer +from typing import Optional, List +from rich.console import Console +from rich.table import Table +from rich import box +from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn +from rich.prompt import Confirm, Prompt + +# 初始化控制台 +console = Console() +file_app = typer.Typer(help="📁 批量文件重命名") + +def is_valid_directory(path: str) -> bool: + """检查路径是否为有效目录""" + return os.path.isdir(path) + +def get_file_list(directory: str, extensions: Optional[List[str]] = None) -> List[str]: + """ + 获取目录中的文件列表 + + Args: + directory: 目标目录 + extensions: 文件扩展名过滤列表(如 ['.txt', '.jpg']) + + Returns: + 文件名列表(仅文件,排除子目录) + """ + files = [] + for f in os.listdir(directory): + file_path = os.path.join(directory, f) + if os.path.isfile(file_path): + if extensions: + file_ext = os.path.splitext(f)[1].lower() + if file_ext in extensions: + files.append(f) + else: + files.append(f) + return files + +@file_app.command("rename") +def batch_rename( + directory: str = typer.Argument(".", help="目标目录路径,默认为当前目录"), + prefix: str = typer.Option(..., "--prefix", "-p", help="文件名前缀"), + start_number: int = typer.Option(1, "--start", "-s", min=1, help="起始编号,默认为 1"), + digits: int = typer.Option(3, "--digits", "-d", min=1, max=10, help="编号位数,默认为 3"), + extension: Optional[List[str]] = typer.Option( + None, "--ext", "-e", help="指定文件扩展名(可多次使用,如 -e .txt -e .jpg)" + ), + recursive: bool = typer.Option(False, "--recursive", "-r", help="递归处理子目录(暂不支持)"), + dry_run: bool = typer.Option(False, "--dry-run", "-n", help="预览重命名结果,不实际执行"), + yes: bool = typer.Option(False, "--yes", "-y", help="跳过确认提示"), +) -> None: + """ + 📝 批量文件重命名,支持自定义前缀、自动编号 + + 示例: + $ devkit-cli file rename --prefix vacation_ --digits 4 --ext .jpg + $ devkit-cli file rename ./photos --prefix img_ --start 100 + $ devkit-cli file rename --prefix test_ --dry-run + """ + # 验证目录 + if not is_valid_directory(directory): + console.print(f"[bold red]❌ 错误:目录 '{directory}' 不存在或无效[/bold red]") + raise typer.Exit(code=1) + + # 规范化扩展名(转换为小写) + if extension: + extension = [ext.lower() if ext.startswith(".") else f".{ext.lower()}" for ext in extension] + + # 获取文件列表 + files = get_file_list(directory, extension) + + if not files: + if extension: + console.print(f"[bold yellow]⚠️ 在目录 '{directory}' 中没有找到指定扩展名的文件[/bold yellow]") + else: + console.print(f"[bold yellow]⚠️ 在目录 '{directory}' 中没有找到任何文件[/bold yellow]") + raise typer.Exit(code=0) + + # 按名称排序 + files.sort() + + # 显示预览表格 + console.print(f"\n[bold magenta]📍 目录:[/bold magenta] {os.path.abspath(directory)}") + console.print(f"[bold magenta]📦 找到文件数:[/bold magenta] {len(files)}\n") + + preview_table = Table( + title="[bold blue]🔍 重命名预览[/bold blue]", + box=box.ROUNDED, + show_header=True, + header_style="bold cyan", + title_justify="left", + ) + preview_table.add_column("#", style="dim", width=4) + preview_table.add_column("原文件名", style="yellow") + preview_table.add_column("→", style="magenta", width=3) + preview_table.add_column("新文件名", style="green") + + rename_map = [] # 存储 (原文件名, 新文件名) 对 + for idx, filename in enumerate(files, start=start_number): + file_name, file_ext = os.path.splitext(filename) + new_filename = f"{prefix}{str(idx).zfill(digits)}{file_ext}" + rename_map.append((filename, new_filename)) + preview_table.add_row(str(idx - start_number + 1), filename, "→", new_filename) + + console.print(preview_table) + + # 如果是预览模式,在此结束 + if dry_run: + console.print("\n[bold cyan]ℹ️ 预览模式,未执行实际重命名操作[/bold cyan]\n") + return + + # 确认操作 + if not yes: + confirm = Confirm.ask( + f"\n[bold yellow]⚠️ 即将重命名 {len(files)} 个文件,是否继续?[/bold yellow]", + default=False, + ) + if not confirm: + console.print("[bold cyan]ℹ️ 操作已取消[/bold cyan]") + raise typer.Exit(code=0) + + # 执行重命名 + success_count = 0 + error_count = 0 + + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + BarColumn(), + TaskProgressColumn(), + console=console, + ) as progress: + task = progress.add_task("[cyan]正在重命名文件...", total=len(rename_map)) + + for old_name, new_name in rename_map: + old_path = os.path.join(directory, old_name) + new_path = os.path.join(directory, new_name) + + try: + # 检查新文件名是否已存在 + if os.path.exists(new_path): + console.print(f"[bold red]❌ 冲突:[/bold red] '{new_name}' 已存在,跳过 '{old_name}'") + error_count += 1 + else: + os.rename(old_path, new_path) + success_count += 1 + except Exception as e: + console.print(f"[bold red]❌ 重命名失败 '{old_name}': {str(e)}[/bold red]") + error_count += 1 + + progress.advance(task) + + # 显示结果摘要 + console.print("\n[bold magenta]════════════════════════════════════════[/bold magenta]") + console.print(f"[bold green]✅ 成功:[/bold green] {success_count} 个文件") + console.print(f"[bold red]❌ 失败:[/bold red] {error_count} 个文件") + console.print("[bold magenta]════════════════════════════════════════[/bold magenta]\n") + +@file_app.command("list") +def list_files( + directory: str = typer.Argument(".", help="目标目录路径,默认为当前目录"), + extension: Optional[List[str]] = typer.Option( + None, "--ext", "-e", help="指定文件扩展名过滤" + ), +) -> None: + """ + 📋 列出目录中的文件,用于预览筛选结果 + + 示例: + $ devkit-cli file list + $ devkit-cli file list ./photos --ext .jpg + """ + if not is_valid_directory(directory): + console.print(f"[bold red]❌ 错误:目录 '{directory}' 不存在或无效[/bold red]") + raise typer.Exit(code=1) + + if extension: + extension = [ext.lower() if ext.startswith(".") else f".{ext.lower()}" for ext in extension] + + files = get_file_list(directory, extension) + files.sort() + + if not files: + console.print(f"[bold yellow]⚠️ 目录 '{directory}' 为空或没有匹配的文件[/bold yellow]") + return + + console.print(f"\n[bold magenta]📍 目录:[/bold magenta] {os.path.abspath(directory)}") + console.print(f"[bold magenta]📦 文件数:[/bold magenta] {len(files)}\n") + + file_table = Table(box=box.ROUNDED, show_header=True, header_style="bold cyan") + file_table.add_column("#", style="dim", width=4) + file_table.add_column("文件名", style="green") + file_table.add_column("大小", style="yellow", justify="right") + + for idx, filename in enumerate(files, 1): + file_path = os.path.join(directory, filename) + size = os.path.getsize(file_path) + # 格式化大小 + if size < 1024: + size_str = f"{size} B" + elif size < 1024 * 1024: + size_str = f"{size / 1024:.1f} KB" + elif size < 1024 * 1024 * 1024: + size_str = f"{size / (1024 * 1024):.1f} MB" + else: + size_str = f"{size / (1024 * 1024 * 1024):.1f} GB" + + file_table.add_row(str(idx), filename, size_str) + + console.print(file_table) + console.print() + +@file_app.callback() +def file_callback() -> None: + """📁 批量文件重命名,支持自定义前缀、自动编号""" + pass diff --git a/devkit_cli/info.py b/devkit_cli/info.py new file mode 100644 index 0000000..931f9ee --- /dev/null +++ b/devkit_cli/info.py @@ -0,0 +1,191 @@ +""" +系统信息模块 - 查看本机系统信息(CPU、内存、磁盘、系统版本) +""" + +import typer +import platform +import psutil +from rich.console import Console +from rich.table import Table +from rich import box + +# 初始化控制台 +console = Console() +info_app = typer.Typer(help="📊 查看本机系统信息") + +def get_size(bytes: int, suffix: str = "B") -> str: + """ + 转换字节数为易读格式 + + Args: + bytes: 字节数 + suffix: 单位后缀 + + Returns: + 格式化后的大小字符串 + """ + factor = 1024 + for unit in ["", "K", "M", "G", "T", "P"]: + if bytes < factor: + return f"{bytes:.2f} {unit}{suffix}" + bytes /= factor + return f"{bytes:.2f} Y{suffix}" + +@info_app.command("show") +def show_info() -> None: + """ + 📊 显示本机系统信息(CPU、内存、磁盘、系统版本) + """ + # 创建主标题 + console.print("\n[bold magenta]╔════════════════════════════════════════════════════════════╗[/bold magenta]") + console.print("[bold magenta]║[/bold magenta] [bold cyan]🖥️ 系统信息概览[/bold cyan] [bold magenta]║[/bold magenta]") + console.print("[bold magenta]╚════════════════════════════════════════════════════════════╝[/bold magenta]\n") + + # 1. 系统信息表格 + sys_table = Table( + title="[bold blue]📋 基本信息[/bold blue]", + box=box.ROUNDED, + show_header=True, + header_style="bold cyan", + title_justify="left", + ) + sys_table.add_column("项目", style="bold green", width=12) + sys_table.add_column("信息", style="yellow") + + uname = platform.uname() + sys_table.add_row("系统", uname.system) + sys_table.add_row("版本", platform.version()) + sys_table.add_row("发行版", platform.platform()) + sys_table.add_row("架构", uname.machine) + sys_table.add_row("主机名", uname.node) + console.print(sys_table) + console.print() + + # 2. CPU信息表格 + cpu_table = Table( + title="[bold blue]⚡ CPU信息[/bold blue]", + box=box.ROUNDED, + show_header=True, + header_style="bold cyan", + title_justify="left", + ) + cpu_table.add_column("项目", style="bold green", width=15) + cpu_table.add_column("信息", style="yellow") + + cpu_freq = psutil.cpu_freq() + cpu_table.add_row("物理核心数", str(psutil.cpu_count(logical=False))) + cpu_table.add_row("逻辑核心数", str(psutil.cpu_count(logical=True))) + cpu_table.add_row("当前频率", f"{cpu_freq.current:.2f} MHz") + cpu_table.add_row("CPU使用率", f"{psutil.cpu_percent(interval=0.5)}%") + + # 每个核心的使用率 + per_cpu = psutil.cpu_percent(percpu=True, interval=0.5) + cpu_usage_str = ", ".join([f"Core{i}: {u}%" for i, u in enumerate(per_cpu)]) + cpu_table.add_row("各核心使用率", cpu_usage_str) + console.print(cpu_table) + console.print() + + # 3. 内存信息表格 + mem_table = Table( + title="[bold blue]🧠 内存信息[/bold blue]", + box=box.ROUNDED, + show_header=True, + header_style="bold cyan", + title_justify="left", + ) + mem_table.add_column("项目", style="bold green", width=12) + mem_table.add_column("总量", style="cyan") + mem_table.add_column("已用", style="yellow") + mem_table.add_column("可用", style="green") + mem_table.add_column("使用率", style="magenta") + + svmem = psutil.virtual_memory() + mem_table.add_row( + "物理内存", + get_size(svmem.total), + get_size(svmem.used), + get_size(svmem.available), + f"{svmem.percent}%", + ) + + # 交换内存 + swap = psutil.swap_memory() + mem_table.add_row( + "交换内存", + get_size(swap.total), + get_size(swap.used), + get_size(swap.free), + f"{swap.percent}%", + ) + console.print(mem_table) + console.print() + + # 4. 磁盘信息表格 + disk_table = Table( + title="[bold blue]💾 磁盘信息[/bold blue]", + box=box.ROUNDED, + show_header=True, + header_style="bold cyan", + title_justify="left", + ) + disk_table.add_column("设备", style="bold green", width=10) + disk_table.add_column("挂载点", style="cyan") + disk_table.add_column("文件系统", style="yellow") + disk_table.add_column("总量", style="blue") + disk_table.add_column("已用", style="magenta") + disk_table.add_column("可用", style="green") + disk_table.add_column("使用率", style="red") + + partitions = psutil.disk_partitions() + for partition in partitions: + try: + usage = psutil.disk_usage(partition.mountpoint) + disk_table.add_row( + partition.device, + partition.mountpoint, + partition.fstype, + get_size(usage.total), + get_size(usage.used), + get_size(usage.free), + f"{usage.percent}%", + ) + except PermissionError: + continue + console.print(disk_table) + console.print() + + # 5. 网络信息 + net_table = Table( + title="[bold blue]🌐 网络接口[/bold blue]", + box=box.ROUNDED, + show_header=True, + header_style="bold cyan", + title_justify="left", + ) + net_table.add_column("接口", style="bold green", width=10) + net_table.add_column("IP地址", style="cyan") + net_table.add_column("MAC地址", style="yellow") + + net_io = psutil.net_if_addrs() + for iface, addrs in net_io.items(): + if iface == "Loopback": + continue + ip = "" + mac = "" + for addr in addrs: + if addr.family == 2: # AF_INET + ip = addr.address + elif addr.family == -1: # AF_PACKET / MAC + mac = addr.address + if ip or mac: + net_table.add_row(iface, ip, mac) + console.print(net_table) + console.print() + + # 底部提示 + console.print("[dim]💡 提示:数据为实时采集,CPU使用率可能有波动[/dim]\n") + +@info_app.callback() +def info_callback() -> None: + """📊 查看本机系统信息(CPU、内存、磁盘、系统版本)""" + pass diff --git a/devkit_cli/todo.py b/devkit_cli/todo.py new file mode 100644 index 0000000..ceffdd0 --- /dev/null +++ b/devkit_cli/todo.py @@ -0,0 +1,397 @@ +""" +任务管理模块 - 本地任务管理,支持添加、查看、标记完成、删除 +""" + +import os +import json +import typer +from typing import Optional, List +from datetime import datetime +from rich.console import Console +from rich.table import Table +from rich import box +from rich.prompt import Prompt, Confirm, IntPrompt + +# 初始化控制台 +console = Console() +todo_app = typer.Typer(help="✅ 本地任务管理") + +# 数据存储文件路径 +TODO_FILE = os.path.join(os.path.expanduser("~"), ".devkit_todo.json") + +class TodoItem: + """任务项数据类""" + def __init__( + self, + id: int, + title: str, + description: str = "", + completed: bool = False, + created_at: str = None, + completed_at: str = None, + priority: str = "medium", + ): + self.id = id + self.title = title + self.description = description + self.completed = completed + self.created_at = created_at or datetime.now().isoformat() + self.completed_at = completed_at + self.priority = priority # high, medium, low + + def to_dict(self) -> dict: + """转换为字典用于JSON存储""" + return { + "id": self.id, + "title": self.title, + "description": self.description, + "completed": self.completed, + "created_at": self.created_at, + "completed_at": self.completed_at, + "priority": self.priority, + } + + @classmethod + def from_dict(cls, data: dict) -> "TodoItem": + """从字典创建任务项""" + return cls(**data) + +class TodoManager: + """任务管理器""" + def __init__(self): + self.items: List[TodoItem] = [] + self.next_id: int = 1 + self.load() + + def load(self) -> None: + """从文件加载任务数据""" + if os.path.exists(TODO_FILE): + try: + with open(TODO_FILE, "r", encoding="utf-8") as f: + data = json.load(f) + self.items = [TodoItem.from_dict(item) for item in data.get("items", [])] + self.next_id = data.get("next_id", 1) + except json.JSONDecodeError: + console.print(f"[bold yellow]⚠️ 任务文件损坏,将创建新文件[/bold yellow]") + self.items = [] + self.next_id = 1 + except Exception as e: + console.print(f"[bold red]❌ 加载任务失败: {str(e)}[/bold red]") + else: + # 文件不存在,初始化空列表 + self.items = [] + self.next_id = 1 + + def save(self) -> None: + """保存任务数据到文件""" + try: + data = { + "next_id": self.next_id, + "items": [item.to_dict() for item in self.items], + } + with open(TODO_FILE, "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=2) + except Exception as e: + console.print(f"[bold red]❌ 保存任务失败: {str(e)}[/bold red]") + + def add(self, title: str, description: str = "", priority: str = "medium") -> TodoItem: + """添加新任务""" + item = TodoItem( + id=self.next_id, + title=title, + description=description, + priority=priority, + ) + self.items.append(item) + self.next_id += 1 + self.save() + return item + + def get(self, item_id: int) -> Optional[TodoItem]: + """根据ID获取任务""" + for item in self.items: + if item.id == item_id: + return item + return None + + def mark_complete(self, item_id: int) -> bool: + """标记任务完成""" + item = self.get(item_id) + if item: + item.completed = True + item.completed_at = datetime.now().isoformat() + self.save() + return True + return False + + def mark_incomplete(self, item_id: int) -> bool: + """标记任务未完成""" + item = self.get(item_id) + if item: + item.completed = False + item.completed_at = None + self.save() + return True + return False + + def delete(self, item_id: int) -> bool: + """删除任务""" + item = self.get(item_id) + if item: + self.items.remove(item) + self.save() + return True + return False + + def clear_completed(self) -> int: + """清除所有已完成的任务""" + count = len([item for item in self.items if item.completed]) + self.items = [item for item in self.items if not item.completed] + self.save() + return count + + def get_all(self, include_completed: bool = True) -> List[TodoItem]: + """获取所有任务""" + if include_completed: + return self.items + return [item for item in self.items if not item.completed] + +# 初始化任务管理器 +todo_manager = TodoManager() + +def get_priority_style(priority: str) -> str: + """获取优先级对应的样式""" + styles = { + "high": "[bold red]🔴 高[/bold red]", + "medium": "[bold yellow]🟡 中[/bold yellow]", + "low": "[bold green]🟢 低[/bold green]", + } + return styles.get(priority.lower(), f"⚪ {priority}") + +def format_datetime(iso_str: str) -> str: + """格式化ISO时间字符串""" + if not iso_str: + return "-" + try: + dt = datetime.fromisoformat(iso_str) + return dt.strftime("%Y-%m-%d %H:%M") + except: + return iso_str + +@todo_app.command("add") +def add_task( + title: str = typer.Argument(..., help="任务标题"), + description: str = typer.Option("", "--desc", "-d", help="任务描述"), + priority: str = typer.Option( + "medium", "--priority", "-p", help="优先级 (high, medium, low)" + ), +) -> None: + """ + ➕ 添加新任务 + + 示例: + $ devkit-cli todo add "完成项目文档" + $ devkit-cli todo add "代码审查" --desc "审查张三的PR" --priority high + """ + # 验证优先级 + priority = priority.lower() + if priority not in ["high", "medium", "low"]: + console.print(f"[bold red]❌ 无效的优先级 '{priority}',请使用: high, medium, low[/bold red]") + raise typer.Exit(code=1) + + item = todo_manager.add(title, description, priority) + + console.print(f"\n[bold green]✅ 任务已添加[/bold green]") + console.print(f"[cyan]ID:[/cyan] {item.id}") + console.print(f"[cyan]标题:[/cyan] {item.title}") + console.print(f"[cyan]描述:[/cyan] {item.description or '-'}") + console.print(f"[cyan]优先级:[/cyan] {get_priority_style(item.priority)}") + console.print() + +@todo_app.command("list") +def list_tasks( + all: bool = typer.Option(False, "--all", "-a", help="显示所有任务(包括已完成)"), + priority: Optional[str] = typer.Option( + None, "--priority", "-p", help="按优先级过滤 (high, medium, low)" + ), +) -> None: + """ + 📋 列出所有任务 + + 示例: + $ devkit-cli todo list + $ devkit-cli todo list --all + $ devkit-cli todo list --priority high + """ + items = todo_manager.get_all(include_completed=all) + + # 按优先级过滤 + if priority: + priority = priority.lower() + items = [item for item in items if item.priority == priority] + + if not items: + console.print("\n[bold yellow]📭 没有任务[/bold yellow]\n") + return + + # 统计信息 + total = len(items) + completed = len([item for item in items if item.completed]) + pending = total - completed + + console.print(f"\n[bold magenta]📊 统计:[/bold magenta] 共 {total} 个任务 " + f"([bold green]{completed} 已完成[/bold green], " + f"[bold yellow]{pending} 进行中[/bold yellow])\n") + + # 创建表格 + table = Table(box=box.ROUNDED, show_header=True, header_style="bold cyan") + table.add_column("ID", style="dim", width=4) + table.add_column("状态", style="bold", width=6) + table.add_column("优先级", width=8) + table.add_column("标题", style="green") + table.add_column("描述", style="yellow") + table.add_column("创建时间", style="cyan", width=16) + + for item in items: + status = "✅" if item.completed else "🔄" + if item.completed: + display_title = f"[strike]{item.title}[/strike]" + else: + display_title = item.title + + table.add_row( + str(item.id), + status, + get_priority_style(item.priority), + display_title, + item.description or "-", + format_datetime(item.created_at), + ) + + console.print(table) + console.print() + +@todo_app.command("show") +def show_task( + task_id: int = typer.Argument(..., help="任务ID"), +) -> None: + """ + 🔍 查看任务详情 + + 示例: + $ devkit-cli todo show 1 + """ + item = todo_manager.get(task_id) + if not item: + console.print(f"[bold red]❌ 未找到ID为 {task_id} 的任务[/bold red]") + raise typer.Exit(code=1) + + console.print(f"\n[bold magenta]════════════════════════════════════════[/bold magenta]") + console.print(f"[bold cyan]📋 任务详情[/bold cyan]") + console.print(f"[bold magenta]════════════════════════════════════════[/bold magenta]") + console.print(f"[cyan]ID:[/cyan] {item.id}") + console.print(f"[cyan]状态:[/cyan] {'✅ 已完成' if item.completed else '🔄 进行中'}") + console.print(f"[cyan]标题:[/cyan] {item.title}") + console.print(f"[cyan]描述:[/cyan] {item.description or '-'}") + console.print(f"[cyan]优先级:[/cyan] {get_priority_style(item.priority)}") + console.print(f"[cyan]创建时间:[/cyan] {format_datetime(item.created_at)}") + if item.completed and item.completed_at: + console.print(f"[cyan]完成时间:[/cyan] {format_datetime(item.completed_at)}") + console.print("[bold magenta]════════════════════════════════════════[/bold magenta]\n") + +@todo_app.command("done") +def mark_done( + task_id: int = typer.Argument(..., help="任务ID"), +) -> None: + """ + ✅ 标记任务为完成 + + 示例: + $ devkit-cli todo done 1 + """ + if todo_manager.mark_complete(task_id): + console.print(f"[bold green]✅ 任务 {task_id} 已标记为完成[/bold green]\n") + else: + console.print(f"[bold red]❌ 未找到ID为 {task_id} 的任务[/bold red]") + raise typer.Exit(code=1) + +@todo_app.command("undone") +def mark_undone( + task_id: int = typer.Argument(..., help="任务ID"), +) -> None: + """ + 🔄 标记任务为未完成 + + 示例: + $ devkit-cli todo undone 1 + """ + if todo_manager.mark_incomplete(task_id): + console.print(f"[bold yellow]🔄 任务 {task_id} 已标记为未完成[/bold yellow]\n") + else: + console.print(f"[bold red]❌ 未找到ID为 {task_id} 的任务[/bold red]") + raise typer.Exit(code=1) + +@todo_app.command("delete") +def delete_task( + task_id: int = typer.Argument(..., help="任务ID"), + yes: bool = typer.Option(False, "--yes", "-y", help="跳过确认提示"), +) -> None: + """ + 🗑️ 删除任务 + + 示例: + $ devkit-cli todo delete 1 + $ devkit-cli todo delete 1 --yes + """ + item = todo_manager.get(task_id) + if not item: + console.print(f"[bold red]❌ 未找到ID为 {task_id} 的任务[/bold red]") + raise typer.Exit(code=1) + + if not yes: + confirm = Confirm.ask( + f"[bold yellow]⚠️ 确定要删除任务 {task_id}: '{item.title}' 吗?[/bold yellow]", + default=False, + ) + if not confirm: + console.print("[bold cyan]ℹ️ 操作已取消[/bold cyan]\n") + return + + if todo_manager.delete(task_id): + console.print(f"[bold green]🗑️ 任务 {task_id} 已删除[/bold green]\n") + else: + console.print(f"[bold red]❌ 删除任务 {task_id} 失败[/bold red]") + raise typer.Exit(code=1) + +@todo_app.command("clear") +def clear_completed( + yes: bool = typer.Option(False, "--yes", "-y", help="跳过确认提示"), +) -> None: + """ + 🧹 清除所有已完成的任务 + + 示例: + $ devkit-cli todo clear + $ devkit-cli todo clear --yes + """ + completed_items = [item for item in todo_manager.items if item.completed] + if not completed_items: + console.print("\n[bold yellow]📭 没有已完成的任务需要清除[/bold yellow]\n") + return + + if not yes: + confirm = Confirm.ask( + f"[bold yellow]⚠️ 确定要清除 {len(completed_items)} 个已完成的任务吗?[/bold yellow]", + default=False, + ) + if not confirm: + console.print("[bold cyan]ℹ️ 操作已取消[/bold cyan]\n") + return + + count = todo_manager.clear_completed() + console.print(f"[bold green]🧹 已清除 {count} 个已完成的任务[/bold green]\n") + +@todo_app.callback() +def todo_callback() -> None: + """✅ 本地任务管理,支持添加、查看、标记完成、删除""" + pass diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ca0baa9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,42 @@ +[project] +name = "devkit-cli" +version = "1.0.0" +description = "开发者工具集命令行工具 - 系统信息、文件重命名、任务管理" +authors = [ + { name="Developer", email="dev@example.com" } +] +readme = "README.md" +license = { file="LICENSE" } +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Operating System :: OS Independent", + "Topic :: Utilities", + "Topic :: System :: Shells", + "Intended Audience :: Developers", + "Environment :: Console", +] + +dependencies = [ + "typer>=0.9.0", + "rich>=13.0.0", + "psutil>=5.9.0", +] + +[project.scripts] +devkit-cli = "devkit_cli.cli:app" +devkit = "devkit_cli.cli:app" + +[project.urls] +Homepage = "https://github.com/example/devkit-cli" +"Bug Tracker" = "https://github.com/example/devkit-cli/issues" + +[tool.setuptools] +packages = ["devkit_cli"]