Files
xian-favor/build/lib/xian_favor/cli.py

430 lines
14 KiB
Python
Raw Normal View History

"""命令行工具"""
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()