evolver-tools 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. evolver_tools/__init__.py +2 -0
  2. evolver_tools/__main__.py +3 -0
  3. evolver_tools/cli.py +89 -0
  4. evolver_tools/vendor/b64/__init__.py +2 -0
  5. evolver_tools/vendor/b64/b64.py +176 -0
  6. evolver_tools/vendor/cal_tool/__init__.py +1 -0
  7. evolver_tools/vendor/cal_tool/cli.py +234 -0
  8. evolver_tools/vendor/chart_cli/__init__.py +444 -0
  9. evolver_tools/vendor/chart_cli/__main__.py +3 -0
  10. evolver_tools/vendor/colors/__init__.py +5 -0
  11. evolver_tools/vendor/colors/__main__.py +97 -0
  12. evolver_tools/vendor/csv_stats/__init__.py +5 -0
  13. evolver_tools/vendor/csv_stats/__main__.py +4 -0
  14. evolver_tools/vendor/csv_stats/analyzer.py +258 -0
  15. evolver_tools/vendor/csv_stats/cli.py +45 -0
  16. evolver_tools/vendor/dirsize/__init__.py +183 -0
  17. evolver_tools/vendor/envcheck/__init__.py +426 -0
  18. evolver_tools/vendor/ff/__init__.py +427 -0
  19. evolver_tools/vendor/ff/__main__.py +3 -0
  20. evolver_tools/vendor/find_dups/__init__.py +7 -0
  21. evolver_tools/vendor/find_dups/cli.py +392 -0
  22. evolver_tools/vendor/hashsum/__init__.py +211 -0
  23. evolver_tools/vendor/hashsum/__main__.py +5 -0
  24. evolver_tools/vendor/http_live/__init__.py +265 -0
  25. evolver_tools/vendor/http_live/__main__.py +2 -0
  26. evolver_tools/vendor/ipinfo/__init__.py +3 -0
  27. evolver_tools/vendor/ipinfo/__main__.py +30 -0
  28. evolver_tools/vendor/jq_lite/__init__.py +257 -0
  29. evolver_tools/vendor/jq_lite/__main__.py +5 -0
  30. evolver_tools/vendor/json2csv/__init__.py +3 -0
  31. evolver_tools/vendor/json2csv/__main__.py +82 -0
  32. evolver_tools/vendor/jsonql/__init__.py +326 -0
  33. evolver_tools/vendor/jsonql/__main__.py +5 -0
  34. evolver_tools/vendor/license_cli/__init__.py +1 -0
  35. evolver_tools/vendor/license_cli/__main__.py +4 -0
  36. evolver_tools/vendor/license_cli/cli.py +289 -0
  37. evolver_tools/vendor/markdown_check/__init__.py +211 -0
  38. evolver_tools/vendor/nb/__init__.py +319 -0
  39. evolver_tools/vendor/nb/__main__.py +3 -0
  40. evolver_tools/vendor/passgen/__init__.py +224 -0
  41. evolver_tools/vendor/portcheck/__init__.py +2 -0
  42. evolver_tools/vendor/portcheck/__main__.py +66 -0
  43. evolver_tools/vendor/project_doctor/__init__.py +412 -0
  44. evolver_tools/vendor/project_doctor/__main__.py +3 -0
  45. evolver_tools/vendor/ren/__init__.py +283 -0
  46. evolver_tools/vendor/ren/__main__.py +3 -0
  47. evolver_tools/vendor/siege_lite/__init__.py +250 -0
  48. evolver_tools/vendor/siege_lite/__main__.py +3 -0
  49. evolver_tools/vendor/smellfinder/__init__.py +376 -0
  50. evolver_tools/vendor/smellfinder/__main__.py +3 -0
  51. evolver_tools/vendor/sqlite_cli/__init__.py +326 -0
  52. evolver_tools/vendor/sqlite_cli/__main__.py +5 -0
  53. evolver_tools/vendor/sysmon/__init__.py +299 -0
  54. evolver_tools/vendor/sysmon/__main__.py +3 -0
  55. evolver_tools/vendor/timer/__init__.py +127 -0
  56. evolver_tools/vendor/treedir/__init__.py +2 -0
  57. evolver_tools/vendor/treedir/__main__.py +128 -0
  58. evolver_tools/vendor/urlparse_tool/__init__.py +3 -0
  59. evolver_tools/vendor/urlparse_tool/cli.py +212 -0
  60. evolver_tools/vendor/web_summary/__init__.py +341 -0
  61. evolver_tools/vendor/web_summary/__main__.py +3 -0
  62. evolver_tools/vendor/wordcount/__init__.py +2 -0
  63. evolver_tools/vendor/wordcount/__main__.py +101 -0
  64. evolver_tools-1.4.0.dist-info/METADATA +107 -0
  65. evolver_tools-1.4.0.dist-info/RECORD +69 -0
  66. evolver_tools-1.4.0.dist-info/WHEEL +5 -0
  67. evolver_tools-1.4.0.dist-info/entry_points.txt +34 -0
  68. evolver_tools-1.4.0.dist-info/licenses/LICENSE +21 -0
  69. evolver_tools-1.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2 @@
1
+ """evolver-tools: 23 CLI tools in one package."""
2
+ __version__ = "1.0.0"
@@ -0,0 +1,3 @@
1
+ """python -m evolver_tools"""
2
+ from .cli import main
3
+ main()
evolver_tools/cli.py ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env python3
2
+ """evolver CLI - Unified interface for all EVOLVER tools."""
3
+
4
+ import sys, importlib, os
5
+
6
+ # Tool registry
7
+ TOOLS = {
8
+ "b64": {"module": "evolver_tools.vendor.b64", "func": "main", "desc": "b64"},
9
+ "cal": {"module": "evolver_tools.vendor.cal_tool.cli", "func": "main", "desc": "Cal"},
10
+ "chart-cli": {"module": "evolver_tools.vendor.chart_cli", "func": "main", "desc": "Chart CLI"},
11
+ "colors": {"module": "evolver_tools.vendor.colors", "func": "main", "desc": "Colors"},
12
+ "csv-stats": {"module": "evolver_tools.vendor.csv_stats.cli", "func": "main", "desc": "csv-stats"},
13
+ "dirsize": {"module": "evolver_tools.vendor.dirsize", "func": "entry", "desc": "Dirsize"},
14
+ "dt": {"module": "evolver_tools.vendor.dt_convert", "func": "main", "desc": "Dt"},
15
+ "ff": {"module": "evolver_tools.vendor.ff", "func": "main", "desc": "Fuzzy Finder"},
16
+ "envcheck": {"module": "evolver_tools.vendor.envcheck", "func": "main", "desc": "Envcheck"},
17
+ "find-dups": {"module": "evolver_tools.vendor.find_dups.cli", "func": "main", "desc": "Find Dups"},
18
+ "hashsum": {"module": "evolver_tools.vendor.hashsum", "func": "main", "desc": "Hashsum"},
19
+ "http-live": {"module": "evolver_tools.vendor.http_live", "func": "main", "desc": "HTTP Live Server"},
20
+ "ipinfo": {"module": "evolver_tools.vendor.ipinfo", "func": "main", "desc": "Ipinfo"},
21
+ "jq-lite": {"module": "evolver_tools.vendor.jq_lite", "func": "main", "desc": "Jq Lite"},
22
+ "json2csv": {"module": "evolver_tools.vendor.json2csv", "func": "main", "desc": "Json2Csv"},
23
+ "jsonql": {"module": "evolver_tools.vendor.jsonql", "func": "main", "desc": "JSONQL"},
24
+ "license-cli": {"module": "evolver_tools.vendor.license_cli.cli", "func": "main", "desc": "License CLI"},
25
+ "markdown-check": {"module": "evolver_tools.vendor.markdown_check", "func": "main", "desc": "Markdown Check"},
26
+ "nb": {"module": "evolver_tools.vendor.nb", "func": "main", "desc": "nb"},
27
+ "passgen": {"module": "evolver_tools.vendor.passgen", "func": "entry", "desc": "Passgen"},
28
+ "portcheck": {"module": "evolver_tools.vendor.portcheck.__main__", "func": "main", "desc": "Portcheck"},
29
+ "project-doctor": {"module": "evolver_tools.vendor.project_doctor", "func": "main", "desc": "Project Doctor"},
30
+ "ren": {"module": "evolver_tools.vendor.ren", "func": "main", "desc": "Ren"},
31
+ "siege-lite": {"module": "evolver_tools.vendor.siege_lite", "func": "main", "desc": "Siege Lite"},
32
+ "smellfinder": {"module": "evolver_tools.vendor.smellfinder", "func": "main", "desc": "Smellfinder"},
33
+ "sqlite-cli": {"module": "evolver_tools.vendor.sqlite_cli", "func": "main", "desc": "Sqlite CLI"},
34
+ "sysmon": {"module": "evolver_tools.vendor.sysmon", "func": "entry", "desc": "Sysmon"},
35
+ "timer": {"module": "evolver_tools.vendor.timer", "func": "entry", "desc": "Timer"},
36
+ "treedir": {"module": "evolver_tools.vendor.treedir.__main__", "func": "main", "desc": "Treedir"},
37
+ "urlparse": {"module": "evolver_tools.vendor.urlparse_tool.cli", "func": "main", "desc": "URL Parse"},
38
+ "web-summary": {"module": "evolver_tools.vendor.web_summary", "func": "main", "desc": "Web Summary"},
39
+ "wordcount": {"module": "evolver_tools.vendor.wordcount.__main__", "func": "main", "desc": "Wordcount"},
40
+ }
41
+
42
+ def list_tools():
43
+ """Display all available tools."""
44
+ print('\x1b[1;36m===== EVOLVER Tools v1.4.0 =====\x1b[0m')
45
+ print()
46
+ for name, info in sorted(TOOLS.items()):
47
+ print(f' \033[1;33m{name:<18}\033[0m {info["desc"]}')
48
+ print()
49
+ print(f' Total: {len(TOOLS)} tools')
50
+ print()
51
+ print('Usage: evolver <toolname> [args...]')
52
+ print(' evolver list')
53
+
54
+ def run_tool(tool_name, args):
55
+ if tool_name not in TOOLS:
56
+ print(f'Unknown tool: {tool_name}')
57
+ sys.exit(1)
58
+ info = TOOLS[tool_name]
59
+ mod_path = info["module"]
60
+ func_name = info["func"]
61
+ old_argv = sys.argv
62
+ sys.argv = [tool_name] + args
63
+ try:
64
+ mod = importlib.import_module(mod_path)
65
+ func = getattr(mod, func_name)
66
+ result = func()
67
+ if result is not None:
68
+ print(result)
69
+ except KeyboardInterrupt:
70
+ pass
71
+ except Exception as e:
72
+ print(f'Error running {tool_name}: {e}', file=sys.stderr)
73
+ sys.exit(1)
74
+ finally:
75
+ sys.argv = old_argv
76
+
77
+ def main():
78
+ if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"):
79
+ list_tools()
80
+ return
81
+ tool_name = sys.argv[1]
82
+ args = sys.argv[2:]
83
+ if tool_name == "list":
84
+ list_tools()
85
+ return
86
+ run_tool(tool_name, args)
87
+
88
+ if __name__ == "__main__":
89
+ main()
@@ -0,0 +1,2 @@
1
+ # b64 package — re-export from single file
2
+ from .b64 import *
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env python3
2
+ """b64 — 零依赖 Base64 编解码工具
3
+
4
+ Usage:
5
+ echo "hello" | b64 encode # 从 stdin 编码
6
+ echo "aGVsbG8K" | b64 decode # 从 stdin 解码
7
+ b64 encode file.txt # 从文件编码
8
+ b64 decode file.b64 # 从文件解码
9
+ b64 -n "hello" encode # 从参数编码
10
+ b64 -n "aGVsbG8K" decode # 从参数解码
11
+ """
12
+
13
+ import sys
14
+ import base64
15
+ import os
16
+
17
+
18
+ STDIN_MODE_AUTO = 'auto' # read stdin if no file and no -n
19
+
20
+
21
+ def encode_bytes(data: bytes) -> str:
22
+ return base64.b64encode(data).decode('ascii')
23
+
24
+
25
+ def decode_bytes(data: bytes, strict: bool = False) -> bytes:
26
+ try:
27
+ if strict:
28
+ # Strict mode: reject non-base64 characters
29
+ return base64.b64decode(data.strip(), validate=True)
30
+ return base64.b64decode(data.strip())
31
+ except Exception as e:
32
+ print(f"Error: invalid base64 input — {e}", file=sys.stderr)
33
+ sys.exit(1)
34
+
35
+ def is_valid_base64(data: bytes) -> bool:
36
+ """Check if data is valid base64 by testing decode+re-encode roundtrip."""
37
+ try:
38
+ # Use strict validation to reject non-base64 chars
39
+ decoded = base64.b64decode(data.strip(), validate=True)
40
+ # Verify roundtrip: re-encode and compare (stripped)
41
+ reencoded = base64.b64encode(decoded).rstrip(b'=')
42
+ cleaned = data.strip().rstrip(b'=')
43
+ return reencoded == cleaned
44
+ except Exception:
45
+ return False
46
+
47
+
48
+ def read_stdin() -> bytes:
49
+ try:
50
+ return sys.stdin.buffer.read()
51
+ except KeyboardInterrupt:
52
+ sys.exit(1)
53
+
54
+
55
+ def main():
56
+ args = sys.argv[1:]
57
+
58
+ # Parse --help / -h
59
+ if not args:
60
+ # Check for piped stdin — auto-detect mode
61
+ if not sys.stdin.isatty():
62
+ raw_stdin = read_stdin()
63
+ if not raw_stdin:
64
+ sys.exit(0)
65
+ if is_valid_base64(raw_stdin):
66
+ result = base64.b64decode(raw_stdin.strip()).decode('utf-8', errors='replace')
67
+ sys.stdout.write(result)
68
+ if not result.endswith('\n'):
69
+ sys.stdout.write('\n')
70
+ else:
71
+ sys.stdout.write(base64.b64encode(raw_stdin).decode('ascii') + '\n')
72
+ return
73
+ print(__doc__.strip())
74
+ return
75
+
76
+ if args[0] in ('-h', '--help'):
77
+ print(__doc__.strip())
78
+ return
79
+
80
+ # Check for -n (inline value)
81
+ if args[0] == '-n':
82
+ if len(args) < 3:
83
+ print("Error: -n requires both <value> and <action>", file=sys.stderr)
84
+ sys.exit(1)
85
+ value = args[1]
86
+ action = args[2]
87
+
88
+ if action == 'encode':
89
+ result = encode_bytes(value.encode('utf-8'))
90
+ elif action == 'decode':
91
+ result = decode_bytes(value)
92
+ result = result.decode('utf-8', errors='replace')
93
+ else:
94
+ print(f"Error: unknown action '{action}' (use encode/decode)", file=sys.stderr)
95
+ sys.exit(1)
96
+
97
+ sys.stdout.write(result)
98
+ if not result.endswith('\n'):
99
+ sys.stdout.write('\n')
100
+ return
101
+
102
+ # Parse <file> <action> or stdin
103
+ action = None
104
+ file_path = None
105
+
106
+ for i, arg in enumerate(args):
107
+ if arg in ('encode', 'decode'):
108
+ action = arg
109
+ elif arg.startswith('-'):
110
+ print(f"Error: unknown option '{arg}'", file=sys.stderr)
111
+ sys.exit(1)
112
+ else:
113
+ file_path = arg
114
+
115
+ if action is None and file_path:
116
+ if not os.path.isfile(file_path):
117
+ print(f"Error: file not found: {file_path}", file=sys.stderr)
118
+ sys.exit(1)
119
+ # Auto-detect: read file, check if it's valid base64
120
+ raw = open(file_path, 'rb').read()
121
+ if is_valid_base64(raw):
122
+ action = 'decode'
123
+ else:
124
+ action = 'encode'
125
+
126
+ if action is None:
127
+ # Remaining args are just the action
128
+ for arg in args:
129
+ if arg in ('encode', 'decode'):
130
+ action = arg
131
+ else:
132
+ file_path = arg
133
+
134
+ if action is None:
135
+ # Try to auto-detect from stdin content if piped
136
+ if not sys.stdin.isatty():
137
+ raw_stdin = read_stdin()
138
+ if not raw_stdin:
139
+ sys.exit(0)
140
+ # Heuristic: if looks like base64, decode; else encode
141
+ try:
142
+ base64.b64decode(raw_stdin.strip())
143
+ result = base64.b64decode(raw_stdin.strip()).decode('utf-8', errors='replace')
144
+ sys.stdout.write(result)
145
+ if not result.endswith('\n'):
146
+ sys.stdout.write('\n')
147
+ except Exception:
148
+ sys.stdout.write(base64.b64encode(raw_stdin).decode('ascii') + '\n')
149
+ return
150
+ print(__doc__.strip())
151
+ return
152
+
153
+ # Read source
154
+ if file_path:
155
+ if not os.path.isfile(file_path):
156
+ print(f"Error: file not found: {file_path}", file=sys.stderr)
157
+ sys.exit(1)
158
+ raw = open(file_path, 'rb').read()
159
+ else:
160
+ raw = read_stdin()
161
+ if not raw:
162
+ sys.exit(0)
163
+
164
+ if action == 'encode':
165
+ result = encode_bytes(raw)
166
+ else:
167
+ decoded = decode_bytes(raw)
168
+ result = decoded.decode('utf-8', errors='replace')
169
+
170
+ sys.stdout.write(result)
171
+ if not result.endswith('\n'):
172
+ sys.stdout.write('\n')
173
+
174
+
175
+ if __name__ == '__main__':
176
+ main()
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env python3
2
+ """cal — 终端日历与日期计算器
3
+
4
+ 零依赖,基于 Python stdlib calendar 和 datetime。
5
+ 支持日历展示、日期差计算、日期加减、年份概览。
6
+ """
7
+
8
+ import argparse
9
+ import calendar
10
+ import datetime
11
+ import sys
12
+ import os
13
+
14
+
15
+ def get_terminal_width():
16
+ """获取终端宽度,默认 80"""
17
+ try:
18
+ return os.get_terminal_size().columns
19
+ except (ValueError, OSError):
20
+ return 80
21
+
22
+
23
+ def print_calendar(year, month, highlight_today=True):
24
+ """打印单月日历"""
25
+ cal = calendar.TextCalendar()
26
+ month_cal = cal.formatmonth(year, month)
27
+
28
+ today = datetime.date.today()
29
+ today_str = f" {today.day} "
30
+ today_hl = f"[{today.day:2d}]"
31
+
32
+ lines = month_cal.split("\n")
33
+ output_lines = []
34
+
35
+ # Header: " January 2026"
36
+ header = lines[0].strip()
37
+ output_lines.append(f"\n \033[1;36m{header}\033[0m")
38
+ output_lines.append("")
39
+
40
+ # Day headers: "Mo Tu We Th Fr Sa Su"
41
+ output_lines.append(" " + lines[1].strip())
42
+
43
+ # Week rows
44
+ for line in lines[2:]:
45
+ if not line.strip():
46
+ continue
47
+ formatted = ""
48
+ # Each day occupies 3 chars: " 1 " or " 10"
49
+ for i in range(0, len(line), 3):
50
+ day_str = line[i : i + 3]
51
+ day = day_str.strip()
52
+ if not day:
53
+ formatted += " "
54
+ continue
55
+ day_num = int(day)
56
+ if highlight_today and today.year == year and today.month == month and today.day == day_num:
57
+ formatted += f"\033[1;33m{day_str}\033[0m"
58
+ else:
59
+ formatted += day_str
60
+ output_lines.append(" " + formatted)
61
+
62
+ sys.stdout.write("\n".join(output_lines))
63
+ sys.stdout.write("\n")
64
+
65
+
66
+ def print_year_calendar(year, highlight_today=True):
67
+ """打印全年日历,每行3个月"""
68
+ today = datetime.date.today()
69
+ cal = calendar.TextCalendar()
70
+
71
+ months_data = []
72
+ for m in range(1, 13):
73
+ month_lines = cal.formatmonth(year, m).split("\n")
74
+ months_data.append(month_lines)
75
+
76
+ # Pad all months to same height
77
+ max_height = max(len(m) for m in months_data)
78
+ for m in months_data:
79
+ while len(m) < max_height:
80
+ m.append("")
81
+
82
+ # Print in groups of 3
83
+ for group_start in range(0, 12, 3):
84
+ group = months_data[group_start : group_start + 3]
85
+ for row_idx in range(max_height):
86
+ parts = []
87
+ for month_idx, month_lines in enumerate(group):
88
+ line = month_lines[row_idx]
89
+ actual_month = group_start + month_idx + 1
90
+ if row_idx == 0:
91
+ # Month name header - center in 20 chars
92
+ month_name = line.strip()
93
+ parts.append(f" {month_name:^20}")
94
+ elif row_idx == 1:
95
+ parts.append(f" {'Mo Tu We Th Fr Sa Su':20}")
96
+ else:
97
+ # Process day numbers with highlight
98
+ formatted = ""
99
+ if highlight_today and today.year == year:
100
+ for i in range(0, len(line), 3):
101
+ day_str = line[i : i + 3] if i + 3 <= len(line) else line[i:]
102
+ day = day_str.strip()
103
+ if day and today.month == actual_month and today.day == int(day):
104
+ formatted += f"\033[1;33m{day_str}\033[0m"
105
+ else:
106
+ formatted += day_str
107
+ else:
108
+ formatted = line
109
+ parts.append(f" {formatted:20}")
110
+ sys.stdout.write("".join(parts) + "\n")
111
+
112
+
113
+ def cmd_calendar(args):
114
+ """日历展示子命令"""
115
+ today = datetime.date.today()
116
+ year = args.year or today.year
117
+ month = args.month or today.month
118
+
119
+ if month:
120
+ print_calendar(year, month, highlight_today=not args.no_highlight)
121
+ else:
122
+ print_year_calendar(year, highlight_today=not args.no_highlight)
123
+
124
+
125
+ def cmd_diff(args):
126
+ """日期差计算"""
127
+ try:
128
+ d1 = datetime.date.fromisoformat(args.diff[0])
129
+ d2 = datetime.date.fromisoformat(args.diff[1])
130
+ except ValueError:
131
+ sys.stderr.write(f"错误: 日期格式无效,请使用 YYYY-MM-DD 格式\n")
132
+ sys.exit(1)
133
+
134
+ delta = abs((d2 - d1).days)
135
+ years = delta // 365
136
+ months_rem = (delta % 365) // 30
137
+ weeks = delta // 7
138
+ days_rem = delta % 7
139
+
140
+ print(f" {args.diff[0]} → {args.diff[1]}")
141
+ print(f" ─{'─' * 40}")
142
+ print(f" 📅 {delta} 天")
143
+ if years or months_rem:
144
+ print(f" ≈ {years} 年 {months_rem} 月")
145
+ if weeks:
146
+ print(f" = {weeks} 周 {days_rem} 天")
147
+ print(f" = {delta * 24} 小时")
148
+ print(f" = {delta * 24 * 60} 分钟")
149
+ print()
150
+
151
+
152
+ def cmd_add(args):
153
+ """日期加减"""
154
+ try:
155
+ d = datetime.date.fromisoformat(args.date)
156
+ except ValueError:
157
+ sys.stderr.write(f"错误: 日期格式无效,请使用 YYYY-MM-DD\n")
158
+ sys.exit(1)
159
+
160
+ result = d + datetime.timedelta(days=args.days)
161
+ direction = "后" if args.days >= 0 else "前"
162
+
163
+ print(f" {args.date} + {args.days}天 = {result.isoformat()}")
164
+ print(f" {args.date} 的 {abs(args.days)}天{direction}是 {result.isoformat()}")
165
+
166
+
167
+ def main():
168
+ parser = argparse.ArgumentParser(
169
+ description="终端日历与日期计算器",
170
+ formatter_class=argparse.RawDescriptionHelpFormatter,
171
+ epilog="""
172
+ 示例:
173
+ cal 显示本月日历
174
+ cal 2025 3 显示2025年3月
175
+ cal --year 2026 显示2026全年日历
176
+ cal --diff 2024-01-01 2024-12-31 计算日期差
177
+ cal --add 2024-01-01 30 增加30天
178
+ cal --add 2024-03-01 -7 减7天
179
+ """,
180
+ )
181
+
182
+ # Diff mode
183
+ parser.add_argument("--diff", nargs=2, metavar=("DATE1", "DATE2"), help="计算两个日期之间的天数")
184
+ # Add mode
185
+ parser.add_argument("--add", nargs=2, metavar=("DATE", "DAYS"), help="日期加减天数")
186
+
187
+ # Calendar display args
188
+ parser.add_argument("year", nargs="?", type=int, default=None, help="年份")
189
+ parser.add_argument("month", nargs="?", type=int, default=None, help="月份 (1-12)")
190
+ parser.add_argument("--year", dest="show_year", action="store_true", help="显示全年日历")
191
+ parser.add_argument("--no-highlight", action="store_true", help="不突出显示今天")
192
+
193
+ args = parser.parse_args()
194
+
195
+ # Route to subcommands
196
+ if args.diff:
197
+ cmd_diff(args)
198
+ elif args.add:
199
+ # Parse days from string
200
+ try:
201
+ date_str, days_str = args.add
202
+ days = int(days_str)
203
+ except ValueError:
204
+ sys.stderr.write("错误: DAYS 必须为整数\n")
205
+ sys.exit(1)
206
+ args.date = date_str
207
+ args.days = days
208
+ cmd_add(args)
209
+ else:
210
+ # Calendar mode
211
+ if args.month is not None and (args.month < 1 or args.month > 12):
212
+ sys.stderr.write("错误: 月份必须在 1-12 之间\n")
213
+ sys.exit(1)
214
+
215
+ calendar.setfirstweekday(calendar.MONDAY)
216
+
217
+ today = datetime.date.today()
218
+ year = args.year or today.year
219
+ month = args.month
220
+
221
+ if args.show_year:
222
+ print_year_calendar(year, highlight_today=not args.no_highlight)
223
+ elif month:
224
+ print_calendar(year, month, highlight_today=not args.no_highlight)
225
+ else:
226
+ # Just year provided? Show whole year
227
+ if args.year:
228
+ print_year_calendar(args.year, highlight_today=not args.no_highlight)
229
+ else:
230
+ print_calendar(today.year, today.month, highlight_today=not args.no_highlight)
231
+
232
+
233
+ if __name__ == "__main__":
234
+ main()