"""命令行工具""" import argparse import sys import json from datetime import datetime from typing import List from .db import db from .config import ITEM_TYPES, TODO_STATUS, PRIORITY_LEVELS def main(): parser = argparse.ArgumentParser( description="Xian Favor - 收藏关注系统", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" 示例: xian_favor add text "这是我的笔记" -t "笔记,重要" xian_favor add link "https://example.com" --title "示例网站" -t "技术" xian_favor add todo "完成项目" -p high -d "2024-12-31" -t "工作" xian_favor add column "https://column.example.com/feed" --title "技术专栏" -t "RSS" xian_favor list xian_favor list --type todo --status pending xian_favor search "关键词" xian_favor show 1 xian_favor edit 1 --status completed --note "已完成" xian_favor done 1 xian_favor delete 1 xian_favor tags xian_favor stats """ ) subparsers = parser.add_subparsers(dest="command", help="命令") # ============ add 命令 ============ add_parser = subparsers.add_parser("add", help="添加新条目") add_parser.add_argument("type", choices=ITEM_TYPES, help="类型: text/link/column/todo") add_parser.add_argument("content", help="内容或URL") add_parser.add_argument("--title", "-T", help="标题") add_parser.add_argument("--url", "-u", help="URL (link/column类型)") add_parser.add_argument("--source", "-s", help="来源") add_parser.add_argument("--status", choices=TODO_STATUS, default="pending", help="状态 (todo)") add_parser.add_argument("--priority", "-p", choices=PRIORITY_LEVELS, default="medium", help="优先级") add_parser.add_argument("--due-date", "-d", help="截止日期 (YYYY-MM-DD)") add_parser.add_argument("--note", "-n", help="备注") add_parser.add_argument("--tags", "-t", help="标签 (逗号分隔)") # ============ list 命令 ============ list_parser = subparsers.add_parser("list", help="列出条目") list_parser.add_argument("--type", choices=ITEM_TYPES, help="类型过滤") list_parser.add_argument("--status", choices=TODO_STATUS, help="状态过滤") list_parser.add_argument("--tag", help="标签过滤") list_parser.add_argument("--limit", "-l", type=int, default=20, help="数量限制") list_parser.add_argument("--json", "-j", action="store_true", help="JSON输出") # ============ show 命令 ============ show_parser = subparsers.add_parser("show", help="查看详情") show_parser.add_argument("id", type=int, help="条目ID") show_parser.add_argument("--json", "-j", action="store_true", help="JSON输出") # ============ edit 命令 ============ edit_parser = subparsers.add_parser("edit", help="编辑条目") edit_parser.add_argument("id", type=int, help="条目ID") edit_parser.add_argument("--title", "-T", help="标题") edit_parser.add_argument("--content", "-c", help="内容") edit_parser.add_argument("--url", "-u", help="URL") edit_parser.add_argument("--source", "-s", help="来源") edit_parser.add_argument("--status", choices=TODO_STATUS, help="状态") edit_parser.add_argument("--priority", "-p", choices=PRIORITY_LEVELS, help="优先级") edit_parser.add_argument("--due-date", "-d", help="截止日期") edit_parser.add_argument("--note", "-n", help="备注") edit_parser.add_argument("--tags", "-t", help="标签 (逗号分隔, 覆盖)") # ============ done 命令 ============ done_parser = subparsers.add_parser("done", help="完成待办") done_parser.add_argument("id", type=int, help="条目ID") # ============ delete 命令 ============ delete_parser = subparsers.add_parser("delete", help="删除条目") delete_parser.add_argument("id", type=int, help="条目ID") delete_parser.add_argument("-f", "--force", action="store_true", help="强制删除不确认") # ============ search 命令 ============ search_parser = subparsers.add_parser("search", help="搜索条目") search_parser.add_argument("keyword", help="关键词") search_parser.add_argument("--type", choices=ITEM_TYPES, help="类型过滤") search_parser.add_argument("--limit", "-l", type=int, default=20, help="数量限制") search_parser.add_argument("--json", "-j", action="store_true", help="JSON输出") # ============ tags 命令 ============ tags_parser = subparsers.add_parser("tags", help="标签管理") tags_parser.add_argument("--delete", "-d", help="删除标签") # ============ stats 命令 ============ subparsers.add_parser("stats", help="统计信息") # ============ serve 命令 ============ serve_parser = subparsers.add_parser("serve", help="启动API服务") serve_parser.add_argument("--host", default="0.0.0.0", help="主机") serve_parser.add_argument("--port", type=int, default=19014, help="端口") # 解析参数 args = parser.parse_args() if not args.command: parser.print_help() return # 执行命令 try: if args.command == "add": cmd_add(args) elif args.command == "list": cmd_list(args) elif args.command == "show": cmd_show(args) elif args.command == "edit": cmd_edit(args) elif args.command == "done": cmd_done(args) elif args.command == "delete": cmd_delete(args) elif args.command == "search": cmd_search(args) elif args.command == "tags": cmd_tags(args) elif args.command == "stats": cmd_stats(args) elif args.command == "serve": cmd_serve(args) except Exception as e: print(f"❌ 错误: {e}", file=sys.stderr) sys.exit(1) # ============ 命令实现 ============ def parse_tags(tags_str: str) -> List[str]: """解析标签字符串""" if not tags_str: return [] return [t.strip() for t in tags_str.split(",") if t.strip()] def format_item(item: dict, brief: bool = True) -> str: """格式化条目显示""" type_icons = { "text": "📝", "link": "🔗", "column": "📰", "todo": "✅" } status_icons = { "pending": "⏳", "in_progress": "🔄", "completed": "✅" } priority_icons = { "low": "🟢", "medium": "🟡", "high": "🟠", "urgent": "🔴" } icon = type_icons.get(item['type'], "📄") status = status_icons.get(item.get('status', ''), '') priority = priority_icons.get(item.get('priority', ''), '') title = item.get('title') or item.get('content', '')[:50] tags_str = f" [{', '.join(item['tags'])}]" if item.get('tags') else "" if brief: return f" {icon} [{item['id']}] {title}{tags_str}" else: lines = [ f"{icon} [{item['id']}] {title}", f" 类型: {item['type']}", ] if item.get('content'): lines.append(f" 内容: {item['content'][:200]}") if item.get('url'): lines.append(f" URL: {item['url']}") if item.get('source'): lines.append(f" 来源: {item['source']}") if item['type'] == 'todo': lines.append(f" 状态: {status} {item.get('status', 'pending')}") lines.append(f" 优先级: {priority} {item.get('priority', 'medium')}") if item.get('due_date'): lines.append(f" 截止: {item['due_date']}") if item.get('note'): lines.append(f" 备注: {item['note']}") if item.get('tags'): lines.append(f" 标签: {', '.join(item['tags'])}") lines.append(f" 创建: {item['created_at'][:19]}") return "\n".join(lines) def cmd_add(args): """添加条目""" tags = parse_tags(args.tags) # 根据类型处理内容 content = args.content url = args.url if args.type == "link": url = url or args.content content = None elif args.type == "column": url = url or args.content content = None item_id = db.create_item( type=args.type, title=args.title, content=content, url=url, source=args.source, status=args.status, priority=args.priority, due_date=args.due_date, note=args.note, tags=tags ) item = db.get_item(item_id) print(f"✅ 创建成功 (ID: {item_id})") print(format_item(item, brief=False)) def cmd_list(args): """列出条目""" items = db.list_items( type=args.type, status=args.status, tag=args.tag, limit=args.limit ) if args.json: print(json.dumps(items, ensure_ascii=False, indent=2)) return if not items: print("📭 没有找到条目") return type_labels = {"text": "文本", "link": "链接", "column": "专栏", "todo": "待办"} filter_desc = [] if args.type: filter_desc.append(f"类型: {type_labels.get(args.type, args.type)}") if args.status: filter_desc.append(f"状态: {args.status}") if args.tag: filter_desc.append(f"标签: {args.tag}") if filter_desc: print(f"📋 筛选: {' | '.join(filter_desc)}") else: print(f"📋 全部条目 ({len(items)} 条)") print("-" * 50) for item in items: print(format_item(item)) def cmd_show(args): """查看详情""" item = db.get_item(args.id) if not item: print(f"❌ 条目不存在: {args.id}") return if args.json: print(json.dumps(item, ensure_ascii=False, indent=2)) return print(format_item(item, brief=False)) def cmd_edit(args): """编辑条目""" update_data = {} if args.title is not None: update_data['title'] = args.title if args.content is not None: update_data['content'] = args.content if args.url is not None: update_data['url'] = args.url if args.source is not None: update_data['source'] = args.source if args.status is not None: update_data['status'] = args.status if args.priority is not None: update_data['priority'] = args.priority if args.due_date is not None: update_data['due_date'] = args.due_date if args.note is not None: update_data['note'] = args.note if args.tags is not None: update_data['tags'] = parse_tags(args.tags) if not update_data: print("❌ 没有指定要更新的字段") return if db.update_item(args.id, **update_data): item = db.get_item(args.id) print(f"✅ 更新成功 (ID: {args.id})") print(format_item(item, brief=False)) else: print(f"❌ 更新失败: 条目不存在或没有变化") def cmd_done(args): """完成待办""" item = db.get_item(args.id) if not item: print(f"❌ 条目不存在: {args.id}") return if item['type'] != 'todo': print(f"❌ 不是待办事项: {args.id}") return if db.update_item(args.id, status='completed'): print(f"✅ 已完成待办 (ID: {args.id})") else: print(f"❌ 操作失败") def cmd_delete(args): """删除条目""" if not args.force: item = db.get_item(args.id) if not item: print(f"❌ 条目不存在: {args.id}") return print(format_item(item, brief=False)) confirm = input("确认删除? [y/N] ") if confirm.lower() != 'y': print("❌ 取消删除") return if db.delete_item(args.id): print(f"✅ 已删除 (ID: {args.id})") else: print(f"❌ 删除失败") def cmd_search(args): """搜索条目""" items = db.list_items( type=args.type, keyword=args.keyword, limit=args.limit ) if args.json: print(json.dumps(items, ensure_ascii=False, indent=2)) return if not items: print(f"🔍 没有找到匹配 '{args.keyword}' 的条目") return print(f"🔍 搜索 '{args.keyword}' ({len(items)} 条)") print("-" * 50) for item in items: print(format_item(item)) def cmd_tags(args): """标签管理""" if args.delete: if db.delete_tag(name=args.delete): print(f"✅ 已删除标签: {args.delete}") else: print(f"❌ 标签不存在: {args.delete}") return tags = db.list_tags() if not tags: print("🏷️ 没有标签") return print(f"🏷️ 标签列表 ({len(tags)} 个)") print("-" * 50) for tag in tags: print(f" • {tag['name']} ({tag['item_count']} 条)") def cmd_stats(args): """统计信息""" stats = db.stats() print("📊 收藏统计") print("-" * 30) print(f"总条目: {stats['total']}") if stats.get('by_type'): print("\n按类型:") type_labels = {"text": "文本", "link": "链接", "column": "专栏", "todo": "待办"} for t, count in stats['by_type'].items(): print(f" • {type_labels.get(t, t)}: {count}") if stats.get('todo_status'): print("\n待办状态:") status_labels = {"pending": "待处理", "in_progress": "进行中", "completed": "已完成"} for s, count in stats['todo_status'].items(): print(f" • {status_labels.get(s, s)}: {count}") print(f"\n标签数: {stats['tags']}") def cmd_serve(args): """启动API服务""" from .api import start_server print(f"🚀 启动API服务: http://{args.host}:{args.port}") start_server(host=args.host, port=args.port) if __name__ == "__main__": main()