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,127 @@
1
+ #!/usr/bin/env python3
2
+ """timer — 终端计时器 / Terminal countdown timer & stopwatch.
3
+
4
+ Zero-dependency CLI timer with countdown, stopwatch, and alarm modes.
5
+ """
6
+
7
+ import sys
8
+ import os
9
+ import time
10
+ import argparse
11
+ import itertools
12
+
13
+
14
+ def entry():
15
+ args = parse_args()
16
+
17
+ if args.stopwatch:
18
+ run_stopwatch()
19
+ elif args.countdown:
20
+ run_countdown(parse_duration(args.countdown))
21
+ elif args.countdown_from:
22
+ run_countdown(args.countdown_from)
23
+ else:
24
+ # Default: show help
25
+ print("Use --stopwatch, --countdown <time>, or --timer <seconds>")
26
+ sys.exit(0)
27
+
28
+
29
+ def format_time(seconds: float) -> str:
30
+ """Format seconds as HH:MM:SS or MM:SS."""
31
+ mins, secs = divmod(int(seconds), 60)
32
+ hours, mins = divmod(mins, 60)
33
+ if hours > 0:
34
+ return f"{hours:02d}:{mins:02d}:{secs:02d}"
35
+ return f"{mins:02d}:{secs:02d}"
36
+
37
+
38
+ def parse_duration(s: str) -> int:
39
+ """Parse duration like '90', '1m30s', '2h' to seconds."""
40
+ s = s.strip().lower()
41
+ if s.isdigit():
42
+ return int(s)
43
+
44
+ total = 0
45
+ num = ""
46
+ for ch in s:
47
+ if ch.isdigit() or ch == ".":
48
+ num += ch
49
+ else:
50
+ if num:
51
+ val = float(num)
52
+ if ch == "h":
53
+ total += int(val * 3600)
54
+ elif ch == "m":
55
+ total += int(val * 60)
56
+ elif ch == "s":
57
+ total += int(val)
58
+ num = ""
59
+ if num:
60
+ total += int(num)
61
+ return max(total, 1)
62
+
63
+
64
+ def run_stopwatch():
65
+ """Run an interactive stopwatch."""
66
+ print("Stopwatch started. Press Ctrl+C to stop.")
67
+ print()
68
+ start = time.time()
69
+ try:
70
+ for _ in itertools.count():
71
+ elapsed = time.time() - start
72
+ print(f"\r {format_time(elapsed)}", end="", flush=True)
73
+ time.sleep(0.1)
74
+ except KeyboardInterrupt:
75
+ elapsed = time.time() - start
76
+ print(f"\n\n Stopped at {format_time(elapsed)}")
77
+ print(f" ({elapsed:.1f} seconds)")
78
+
79
+
80
+ def run_countdown(seconds: int):
81
+ """Run a countdown timer."""
82
+ print(f"Countdown: {format_time(seconds)}")
83
+ print()
84
+ try:
85
+ remaining = seconds
86
+ while remaining > 0:
87
+ print(f"\r {format_time(remaining)}", end="", flush=True)
88
+ time.sleep(1)
89
+ remaining -= 1
90
+ print(f"\r 00:00")
91
+ print()
92
+ # Alarm notification
93
+ bell = "\a" * 3
94
+ print(f"{bell} TIME'S UP! ({format_time(seconds)})")
95
+ # Keep beeping for a bit
96
+ for _ in range(5):
97
+ print("\a", end="", flush=True)
98
+ time.sleep(0.5)
99
+ except KeyboardInterrupt:
100
+ elapsed = seconds - (remaining if 'remaining' in dir() else seconds)
101
+ print(f"\n\n Stopped at {format_time(elapsed)}")
102
+
103
+
104
+ def parse_args():
105
+ parser = argparse.ArgumentParser(
106
+ description="timer — 终端计时器 / Terminal timer & stopwatch",
107
+ formatter_class=argparse.RawDescriptionHelpFormatter,
108
+ epilog="""Examples:
109
+ timer --stopwatch # Start stopwatch
110
+ timer --countdown 90 # Countdown 90 seconds
111
+ timer --countdown 5m # Countdown 5 minutes
112
+ timer --countdown 1h30m # Countdown 1 hour 30 minutes
113
+ timer -t 300 # Countdown 300 seconds
114
+ """)
115
+ parser.add_argument("-s", "--stopwatch", action="store_true",
116
+ help="Start stopwatch mode")
117
+ parser.add_argument("-c", "--countdown", type=str, nargs="?", const="60",
118
+ metavar="DURATION",
119
+ help="Countdown timer (default: 60s). Format: 90, 5m, 1h30m")
120
+ parser.add_argument("-t", "--timer", dest="countdown_from", type=int,
121
+ metavar="SECONDS",
122
+ help="Countdown from N seconds")
123
+ return parser.parse_args()
124
+
125
+
126
+ if __name__ == "__main__":
127
+ entry()
@@ -0,0 +1,2 @@
1
+ """treedir — 目录树可视化"""
2
+ __version__ = "1.0.0"
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env python3
2
+ """treedir — 目录树可视化工具。零外部依赖(替代 tree 命令)。"""
3
+ import os
4
+ import sys
5
+
6
+ PIPE = "│ "
7
+ BRANCH = "├── "
8
+ LAST = "└── "
9
+
10
+ def build_tree(root_path, max_depth=3, show_hidden=False, dirs_only=False):
11
+ """递归构建目录树"""
12
+ root_path = os.path.abspath(root_path)
13
+ if not os.path.isdir(root_path):
14
+ print(f"❌ 不是目录: {root_path}", file=sys.stderr)
15
+ return
16
+
17
+ basename = os.path.basename(root_path) or root_path
18
+ print(basename)
19
+
20
+ def _walk(dirpath, prefix="", depth=0):
21
+ if depth >= max_depth:
22
+ return
23
+
24
+ try:
25
+ entries = sorted(os.listdir(dirpath))
26
+ except PermissionError:
27
+ print(f"{prefix}{LAST} [权限不足]")
28
+ return
29
+
30
+ if not show_hidden:
31
+ entries = [e for e in entries if not e.startswith(".")]
32
+
33
+ # Separate dirs and files
34
+ dirs = []
35
+ files = []
36
+ for e in entries:
37
+ full = os.path.join(dirpath, e)
38
+ if os.path.isdir(full):
39
+ dirs.append(e)
40
+ else:
41
+ files.append(e)
42
+
43
+ if dirs_only:
44
+ items = dirs
45
+ else:
46
+ items = dirs + files
47
+
48
+ for i, entry in enumerate(items):
49
+ is_last = (i == len(items) - 1)
50
+ connector = LAST if is_last else BRANCH
51
+ full = os.path.join(dirpath, entry)
52
+
53
+ if os.path.isdir(full):
54
+ print(f"{prefix}{connector}{entry}/")
55
+ next_prefix = prefix + (" " if is_last else PIPE)
56
+ _walk(full, next_prefix, depth + 1)
57
+ else:
58
+ # Show file size
59
+ try:
60
+ size = os.path.getsize(full)
61
+ size_str = format_size(size)
62
+ print(f"{prefix}{connector}{entry} ({size_str})")
63
+ except OSError:
64
+ print(f"{prefix}{connector}{entry}")
65
+
66
+ _walk(root_path)
67
+ print(f"\n{count_items(root_path, show_hidden, dirs_only)}")
68
+
69
+ def count_items(root_path, show_hidden=False, dirs_only=False):
70
+ """统计目录内容"""
71
+ dcount = 0
72
+ fcount = 0
73
+ for root, dirs, files in os.walk(root_path):
74
+ if not show_hidden:
75
+ dirs[:] = [d for d in dirs if not d.startswith(".")]
76
+ files = [f for f in files if not f.startswith(".")]
77
+ dcount += len(dirs)
78
+ if not dirs_only:
79
+ fcount += len(files)
80
+ # Only first level for dirs_only
81
+ if dirs_only:
82
+ break
83
+ if dirs_only:
84
+ return f"{dcount} 个目录"
85
+ return f"{dcount} 个目录, {fcount} 个文件"
86
+
87
+ def format_size(size):
88
+ for unit in ("B", "KB", "MB", "GB"):
89
+ if size < 1024:
90
+ return f"{size:.0f} {unit}"
91
+ size /= 1024
92
+ return f"{size:.1f} TB"
93
+
94
+ def main():
95
+ args = sys.argv[1:]
96
+
97
+ if "-h" in args or "--help" in args:
98
+ print("用法: treedir [选项] [目录]")
99
+ print("选项:")
100
+ print(" -d, --max-depth N 最大深度 (默认: 3)")
101
+ print(" -a, --all 显示隐藏文件")
102
+ print(" --dirs-only 只显示目录")
103
+ print(" 如果不指定目录,使用当前目录")
104
+ return
105
+
106
+ root = "."
107
+ max_depth = 3
108
+ show_hidden = False
109
+ dirs_only = False
110
+
111
+ i = 0
112
+ while i < len(args):
113
+ if args[i] in ("-d", "--max-depth"):
114
+ i += 1
115
+ if i < len(args):
116
+ max_depth = int(args[i])
117
+ elif args[i] in ("-a", "--all"):
118
+ show_hidden = True
119
+ elif args[i] == "--dirs-only":
120
+ dirs_only = True
121
+ else:
122
+ root = args[i]
123
+ i += 1
124
+
125
+ build_tree(root, max_depth, show_hidden, dirs_only)
126
+
127
+ if __name__ == "__main__":
128
+ main()
@@ -0,0 +1,3 @@
1
+ """urlparse_tool — URL parser/debugger CLI tool (stdlib only)."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,212 @@
1
+ """CLI entry point for urlparse — URL parser/debugger.
2
+
3
+ Usage:
4
+ urlparse https://user:pass@example.com:8080/path;params?a=1&b=2#frag
5
+ echo 'https://example.com/path?q=hello' | urlparse
6
+ urlparse --encode 'hello world'
7
+ urlparse --decode 'hello+world'
8
+ """
9
+
10
+ import argparse
11
+ import sys
12
+ from urllib.parse import (
13
+ urlparse,
14
+ urlencode,
15
+ parse_qs,
16
+ parse_qsl,
17
+ quote,
18
+ unquote,
19
+ urlunparse,
20
+ )
21
+
22
+
23
+ def parse_url(url: str) -> dict:
24
+ """Parse a URL and return all components."""
25
+ parsed = urlparse(url)
26
+ result = {
27
+ "url": url,
28
+ "scheme": parsed.scheme or "",
29
+ "netloc": parsed.netloc or "",
30
+ "username": parsed.username or "",
31
+ "password": parsed.password or "",
32
+ "hostname": parsed.hostname or "",
33
+ "port": parsed.port,
34
+ "path": parsed.path or "",
35
+ "params": parsed.params or "",
36
+ "query": parsed.query or "",
37
+ "fragment": parsed.fragment or "",
38
+ }
39
+
40
+ # Parse query string into key-value pairs
41
+ if parsed.query:
42
+ result["query_pairs"] = parse_qs(parsed.query, keep_blank_values=True)
43
+ result["query_pairs_ordered"] = parse_qsl(parsed.query, keep_blank_values=True)
44
+ else:
45
+ result["query_pairs"] = {}
46
+ result["query_pairs_ordered"] = []
47
+
48
+ # Reconstruct the URL (canonical form)
49
+ reconstructed = urlunparse(
50
+ (
51
+ parsed.scheme,
52
+ parsed.netloc,
53
+ parsed.path,
54
+ parsed.params,
55
+ parsed.query,
56
+ parsed.fragment,
57
+ )
58
+ )
59
+ result["reconstructed"] = reconstructed
60
+
61
+ return result
62
+
63
+
64
+ def format_parse_result(result: dict) -> str:
65
+ """Format parsed URL components into a human-readable string."""
66
+ lines = []
67
+ lines.append(f" URL: {result['url']}")
68
+ lines.append(f" Reconstructed: {result['reconstructed']}")
69
+ lines.append(" ───────────────────────────────────────────")
70
+ lines.append(f" Scheme: {result['scheme']}")
71
+ lines.append(f" Netloc: {result['netloc']}")
72
+ lines.append(f" Username: {result['username']}")
73
+ lines.append(f" Password: {'●' * max(1, len(result['password'])) if result['password'] else ''}")
74
+ lines.append(f" Hostname: {result['hostname']}")
75
+ lines.append(f" Port: {result['port'] if result['port'] is not None else ''}")
76
+ lines.append(f" Path: {result['path']}")
77
+ lines.append(f" Params: {result['params']}")
78
+ lines.append(f" Query: {result['query']}")
79
+ lines.append(f" Fragment: {result['fragment']}")
80
+
81
+ if result["query_pairs"]:
82
+ lines.append(" ───────────────────────────────────────────")
83
+ lines.append(" Query parameters:")
84
+ for key, values in result["query_pairs"].items():
85
+ for val in values:
86
+ lines.append(f" {key} = {val}")
87
+
88
+ return "\n".join(lines)
89
+
90
+
91
+ def format_parse_result_json(result: dict) -> str:
92
+ """Format parsed URL components as JSON."""
93
+ import json
94
+
95
+ output = {
96
+ "url": result["url"],
97
+ "scheme": result["scheme"],
98
+ "netloc": result["netloc"],
99
+ "username": result["username"],
100
+ "password": result["password"],
101
+ "hostname": result["hostname"],
102
+ "port": result["port"],
103
+ "path": result["path"],
104
+ "params": result["params"],
105
+ "query": result["query"],
106
+ "fragment": result["fragment"],
107
+ }
108
+
109
+ if result["query_pairs"]:
110
+ output["query_parameters"] = dict(result["query_pairs"])
111
+
112
+ return json.dumps(output, indent=2)
113
+
114
+
115
+ def do_encode(text: str) -> str:
116
+ """URL-encode a string."""
117
+ return quote(text, safe="")
118
+
119
+
120
+ def do_decode(text: str) -> str:
121
+ """URL-decode a string."""
122
+ return unquote(text, encoding="utf-8", errors="replace")
123
+
124
+
125
+ def read_stdin() -> str:
126
+ """Read raw input from stdin (pipe mode)."""
127
+ if not sys.stdin.isatty():
128
+ return sys.stdin.read().strip()
129
+ return ""
130
+
131
+
132
+ def main() -> None:
133
+ parser = argparse.ArgumentParser(
134
+ description="URL parser/debugger — parse, encode, and decode URLs.",
135
+ formatter_class=argparse.RawDescriptionHelpFormatter,
136
+ epilog=(
137
+ "Examples:\n"
138
+ " urlparse https://example.com/path?q=hello#section\n"
139
+ " echo 'https://example.com/search?q=hello+world' | urlparse\n"
140
+ " urlparse --encode 'hello world'\n"
141
+ " urlparse --decode 'hello+world%21'\n"
142
+ " urlparse --json https://example.com?foo=1&bar=2\n"
143
+ ),
144
+ )
145
+
146
+ parser.add_argument(
147
+ "url",
148
+ nargs="?",
149
+ help="URL to parse (omit to read from stdin)",
150
+ )
151
+ parser.add_argument(
152
+ "--encode",
153
+ nargs="?",
154
+ const=True,
155
+ metavar="TEXT",
156
+ help="URL-encode the given text (or pipe input)",
157
+ )
158
+ parser.add_argument(
159
+ "--decode",
160
+ nargs="?",
161
+ const=True,
162
+ metavar="TEXT",
163
+ help="URL-decode the given text (or pipe input)",
164
+ )
165
+ parser.add_argument(
166
+ "--json",
167
+ action="store_true",
168
+ help="Output parse results as JSON",
169
+ )
170
+
171
+ args = parser.parse_args()
172
+
173
+ # --- Encode mode ---
174
+ if args.encode is not None:
175
+ text = args.encode if isinstance(args.encode, str) else read_stdin()
176
+ if not text:
177
+ # If --encode was used as a flag with pipe, read_stdin already tried
178
+ text = read_stdin()
179
+ if not text:
180
+ print("error: --encode requires text argument or pipe input", file=sys.stderr)
181
+ sys.exit(1)
182
+ print(do_encode(text))
183
+ return
184
+
185
+ # --- Decode mode ---
186
+ if args.decode is not None:
187
+ text = args.decode if isinstance(args.decode, str) else read_stdin()
188
+ if not text:
189
+ text = read_stdin()
190
+ if not text:
191
+ print("error: --decode requires text argument or pipe input", file=sys.stderr)
192
+ sys.exit(1)
193
+ print(do_decode(text))
194
+ return
195
+
196
+ # --- Parse mode ---
197
+ url = args.url or read_stdin()
198
+
199
+ if not url:
200
+ parser.print_help()
201
+ sys.exit(1)
202
+
203
+ result = parse_url(url)
204
+
205
+ if args.json:
206
+ print(format_parse_result_json(result))
207
+ else:
208
+ print(format_parse_result(result))
209
+
210
+
211
+ if __name__ == "__main__":
212
+ main()