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,444 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ chart-cli — 终端图表生成器
4
+ 用 Unicode 在终端绘制条形图、折线图、饼图。
5
+ """
6
+
7
+ import sys
8
+ import json
9
+ import math
10
+ from collections import Counter
11
+
12
+ # Unicode block characters
13
+ BAR_FULL = '█'
14
+ BAR_HALF = '▌'
15
+ BAR_EMPTY = '░'
16
+ H_LINE = '─'
17
+ V_LINE = '│'
18
+ CORNER_TL = '┌'
19
+ CORNER_TR = '┐'
20
+ CORNER_BL = '└'
21
+ CORNER_BR = '┘'
22
+ CROSS = '┼'
23
+ TEE_DOWN = '┬'
24
+ TEE_UP = '┴'
25
+ TEE_RIGHT = '├'
26
+ TEE_LEFT = '┤'
27
+
28
+ # 8-level block for fine-grained bars
29
+ BLOCKS = [' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█']
30
+
31
+ def red(s): return f"\033[91m{s}\033[0m"
32
+ def green(s): return f"\033[92m{s}\033[0m"
33
+ def yellow(s): return f"\033[93m{s}\033[0m"
34
+ def cyan(s): return f"\033[96m{s}\033[0m"
35
+ def dim(s): return f"\033[2m{s}\033[0m"
36
+ def bold(s): return f"\033[1m{s}\033[0m"
37
+
38
+ def parse_data(source):
39
+ """Parse data from string or dict"""
40
+ if isinstance(source, list):
41
+ return source
42
+ if isinstance(source, dict):
43
+ return list(source.items())
44
+ # Try parsing as JSON
45
+ try:
46
+ parsed = json.loads(source)
47
+ if isinstance(parsed, list):
48
+ # Handle flat alternating array: ["key1", val1, "key2", val2, ...]
49
+ if parsed and not isinstance(parsed[0], (list, tuple)):
50
+ if len(parsed) % 2 == 0:
51
+ return [(str(parsed[i]), float(parsed[i+1])) for i in range(0, len(parsed), 2)]
52
+ else:
53
+ raise ValueError("数组格式应为 [键1, 值1, 键2, 值2, ...] (偶数个元素)")
54
+ return parsed
55
+ if isinstance(parsed, dict):
56
+ return list(parsed.items())
57
+ except json.JSONDecodeError:
58
+ pass
59
+ # Try parsing as key:value lines
60
+ result = []
61
+ # Split by commas first (for inline lists)
62
+ lines = []
63
+ for line in source.strip().split('\n'):
64
+ if ',' in line and not line.startswith('{') and not line.startswith('['):
65
+ # Could be comma-separated pairs
66
+ parts = line.split(',')
67
+ for p in parts:
68
+ p = p.strip()
69
+ if p:
70
+ lines.append(p)
71
+ else:
72
+ if line.strip():
73
+ lines.append(line.strip())
74
+
75
+ for raw_line in lines:
76
+ if ':' in raw_line:
77
+ parts = raw_line.split(':', 1)
78
+ key = parts[0].strip()
79
+ try:
80
+ val = float(parts[1].strip())
81
+ result.append((key, val))
82
+ except ValueError:
83
+ pass
84
+ elif '\t' in raw_line:
85
+ parts = raw_line.split('\t')
86
+ try:
87
+ val = float(parts[-1].strip())
88
+ key = ' '.join(parts[:-1]).strip()
89
+ result.append((key, val))
90
+ except ValueError:
91
+ pass
92
+ else:
93
+ # Try as raw number
94
+ try:
95
+ val = float(raw_line.strip())
96
+ result.append((raw_line.strip(), val))
97
+ except ValueError:
98
+ pass
99
+ return result
100
+
101
+
102
+ def draw_bar_chart(data, width=40, height=12, horizontal=False, sort=False, show_values=True):
103
+ """Draw a bar chart"""
104
+ if not data:
105
+ return "(无数据)"
106
+
107
+ if sort:
108
+ data = sorted(data, key=lambda x: x[1], reverse=True)
109
+
110
+ labels = [str(d[0])[:20] for d in data]
111
+ values = [float(d[1]) for d in data]
112
+
113
+ max_val = max(values) if values else 1
114
+ min_val = min(values)
115
+ lines = []
116
+
117
+ if horizontal:
118
+ # Horizontal bar chart
119
+ max_label_len = max(len(l) for l in labels)
120
+ max_label_len = min(max_label_len, 20)
121
+
122
+ lines.append(f" {bold('条形图 (水平)')}")
123
+ lines.append("")
124
+
125
+ for i, (label, val) in enumerate(zip(labels, values)):
126
+ bar_width = int((val / max_val) * width) if max_val > 0 else 0
127
+ bar = BAR_FULL * bar_width
128
+ val_str = f" {val}" if show_values else ""
129
+ padded_label = label.ljust(max_label_len)[:max_label_len]
130
+ lines.append(f" {dim(padded_label)} {dim('│')} {green(bar)}{val_str}")
131
+
132
+ lines.append(f" {dim('─' * (max_label_len + 3 + width))}")
133
+ lines.append(f" {dim(('0').rjust(max_label_len + 3))} {max_val})")
134
+
135
+ else:
136
+ # Vertical bar chart
137
+ max_label_len = max(len(l) for l in labels)
138
+ n_bars = len(labels)
139
+
140
+ # Calculate available height for bars (leave room for labels + values)
141
+ bar_area_height = max(5, height - 3)
142
+
143
+ lines.append(f" {bold('条形图 (垂直)')}")
144
+ lines.append("")
145
+
146
+ # Draw bars top to bottom
147
+ for row in range(bar_area_height, 0, -1):
148
+ threshold = max_val * (row - 1) / bar_area_height if bar_area_height > 0 else 0
149
+
150
+ line_parts = [" "]
151
+ for val in values:
152
+ if val >= threshold:
153
+ if val >= max_val * row / bar_area_height:
154
+ line_parts.append(f" {BAR_FULL} ")
155
+ else:
156
+ line_parts.append(f" {BAR_HALF} ")
157
+ else:
158
+ line_parts.append(f" ")
159
+ lines.append(''.join(line_parts))
160
+
161
+ # Axis line
162
+ lines.append(f" {'─' * (n_bars * 3 + 1)}")
163
+
164
+ # Labels
165
+ line_parts = [" "]
166
+ for label in labels:
167
+ short = label[:3]
168
+ line_parts.append(f" {short} ")
169
+ lines.append(''.join(line_parts))
170
+
171
+ # Value labels
172
+ if show_values:
173
+ line_parts = [" "]
174
+ for val in values:
175
+ s = str(int(val)) if val == int(val) else f"{val:.1f}"
176
+ line_parts.append(f"{s:>3} ")
177
+ lines.append(''.join(line_parts))
178
+
179
+ return '\n'.join(lines)
180
+
181
+
182
+ def draw_line_chart(data, width=50, height=14):
183
+ """Draw a line chart"""
184
+ if not data:
185
+ return "(无数据)"
186
+
187
+ # If data is list of (x, y) tuples
188
+ if isinstance(data[0], (list, tuple)) and len(data[0]) >= 2:
189
+ points = [(float(d[0]), float(d[1])) for d in data]
190
+ else:
191
+ # Assume index as x
192
+ points = [(float(i), float(d[1]) if isinstance(d, (list, tuple)) else float(d))
193
+ for i, d in enumerate(data)]
194
+
195
+ if len(points) < 2:
196
+ return "(至少需要2个数据点)"
197
+
198
+ xs = [p[0] for p in points]
199
+ ys = [p[1] for p in points]
200
+ min_x, max_x = min(xs), max(xs)
201
+ min_y, max_y = min(ys), max(ys)
202
+ range_y = max_y - min_y if max_y != min_y else 1
203
+ range_x = max_x - min_x if max_x != min_x else 1
204
+
205
+ chart_width = max(20, width - 6) # Leave room for y-axis labels
206
+ chart_height = max(5, height - 3)
207
+
208
+ lines = []
209
+ lines.append(f" {bold('折线图')}")
210
+ lines.append("")
211
+
212
+ # Create the chart grid
213
+ grid = [[' ' for _ in range(chart_width)] for _ in range(chart_height)]
214
+
215
+ # Plot points
216
+ for x, y in points:
217
+ col = int((x - min_x) / range_x * (chart_width - 1))
218
+ row = int((max_y - y) / range_y * (chart_height - 1))
219
+ col = min(max(0, col), chart_width - 1)
220
+ row = min(max(0, row), chart_height - 1)
221
+ grid[row][col] = '•'
222
+
223
+ # Draw lines between points
224
+ for i in range(len(points) - 1):
225
+ x1, y1 = points[i]
226
+ x2, y2 = points[i+1]
227
+ c1 = int((x1 - min_x) / range_x * (chart_width - 1))
228
+ c2 = int((x2 - min_x) / range_x * (chart_width - 1))
229
+ r1 = int((max_y - y1) / range_y * (chart_height - 1))
230
+ r2 = int((max_y - y2) / range_y * (chart_height - 1))
231
+
232
+ # Bresenham-like line drawing
233
+ steps = max(abs(c2 - c1), abs(r2 - r1))
234
+ if steps > 1:
235
+ for t in range(1, steps):
236
+ frac = t / steps
237
+ cx = int(c1 + (c2 - c1) * frac)
238
+ cy = int(r1 + (r2 - r1) * frac)
239
+ cx = min(max(0, cx), chart_width - 1)
240
+ cy = min(max(0, cy), chart_height - 1)
241
+ if grid[cy][cx] == ' ':
242
+ grid[cy][cx] = '·'
243
+
244
+ # Y-axis labels
245
+ y_labels = []
246
+ for i in range(chart_height):
247
+ val = max_y - (range_y * i / (chart_height - 1))
248
+ y_labels.append(f"{val:>6.1f}")
249
+
250
+ # Print chart
251
+ for i in range(chart_height):
252
+ label = y_labels[i]
253
+ row_line = ''.join(grid[i])
254
+ lines.append(f" {dim(label)} {dim(V_LINE)} {cyan(row_line)}")
255
+
256
+ # X-axis
257
+ x_label_fmt = f"{min_x:.1f}".rjust(chart_width // 2) + f"{max_x:.1f}".rjust(chart_width // 2)
258
+ lines.append(f" {dim(' ')} {dim('└')}{dim('─' * chart_width)}")
259
+ lines.append(f" {dim(' ')} {x_label_fmt}")
260
+
261
+ return '\n'.join(lines)
262
+
263
+
264
+ def draw_pie_chart(data, radius=8):
265
+ """Draw a pie chart using braille/block characters"""
266
+ if not data:
267
+ return "(无数据)"
268
+
269
+ total = sum(float(d[1]) for d in data)
270
+ if total == 0:
271
+ return "(总和为零)"
272
+
273
+ labels = [str(d[0])[:15] for d in data]
274
+ values = [float(d[1]) for d in data]
275
+
276
+ # Calculate angles (in radians, starting from top)
277
+ angles = []
278
+ cumulative = 0
279
+ for val in values:
280
+ angle = (val / total) * 2 * math.pi
281
+ cumulative += angle
282
+ angles.append(cumulative)
283
+
284
+ # Round to first angle = 0
285
+ angles = [0] + angles
286
+
287
+ # Available colors
288
+ colors = [
289
+ '\033[91m', '\033[93m', '\033[92m', '\033[96m',
290
+ '\033[94m', '\033[95m', '\033[38;5;208m', '\033[38;5;45m',
291
+ ]
292
+ reset = '\033[0m'
293
+
294
+ diameter = radius * 2 + 1
295
+ center_x = radius
296
+ center_y = radius
297
+
298
+ lines = []
299
+ lines.append(f" {bold('饼图')}")
300
+ lines.append("")
301
+
302
+ # Generate pie
303
+ pie_chars = []
304
+ for y in range(diameter):
305
+ row_chars = []
306
+ for x in range(diameter):
307
+ # Distance from center
308
+ dx = x - center_x
309
+ dy = y - center_y
310
+ dist = math.sqrt(dx * dx + dy * dy)
311
+
312
+ if dist < radius - 0.5:
313
+ # Inside: check angle
314
+ angle = math.atan2(dx, -dy) # Atan2, with top as 0
315
+ if angle < 0:
316
+ angle += 2 * math.pi
317
+
318
+ # Find which segment
319
+ seg_idx = -1
320
+ for i in range(len(angles) - 1):
321
+ if angles[i] <= angle < angles[i + 1]:
322
+ seg_idx = i
323
+ break
324
+ if seg_idx == -1 and angle >= angles[-2]:
325
+ seg_idx = len(angles) - 2
326
+
327
+ if 0 <= seg_idx < len(colors):
328
+ # Draw filled with dithering
329
+ # Use different chars for different segments
330
+ chars = ['█', '▓', '▒', '░']
331
+ c = colors[seg_idx % len(colors)]
332
+ row_chars.append(f"{c}{chr(0x2588)}{reset}")
333
+ else:
334
+ row_chars.append(' ')
335
+ elif dist < radius + 0.5:
336
+ row_chars.append(f"{dim('·')} ")
337
+ else:
338
+ row_chars.append(' ')
339
+ pie_chars.append(''.join(row_chars))
340
+
341
+ # Print pie + legend side by side
342
+ legend_lines = []
343
+ for i in range(len(data)):
344
+ c = colors[i % len(colors)]
345
+ pct = values[i] / total * 100
346
+ legend_lines.append(f" {c}█{reset} {labels[i]:<15} {values[i]:>8} ({pct:4.1f}%)")
347
+
348
+ max_lines = max(len(pie_chars), len(legend_lines))
349
+ for i in range(max_lines):
350
+ left = pie_chars[i] if i < len(pie_chars) else ' ' * (diameter * 2)
351
+ right = legend_lines[i] if i < len(legend_lines) else ''
352
+ print(f" {left} {right}")
353
+
354
+ return "\n" # Already printed during drawing
355
+
356
+
357
+ def draw_histogram_chart(data, bins=10, width=40):
358
+ """Draw a histogram from raw values"""
359
+ if not data:
360
+ return "(无数据)"
361
+
362
+ values = [float(d) if isinstance(d, (int, float)) else float(d[1]) for d in data]
363
+ if not values:
364
+ return "(无数据)"
365
+
366
+ min_v = min(values)
367
+ max_v = max(values)
368
+ if min_v == max_v:
369
+ return f" 所有值相同: {min_v}"
370
+
371
+ range_v = max_v - min_v
372
+ bucket_size = range_v / bins
373
+ buckets = [0] * bins
374
+
375
+ for v in values:
376
+ idx = min(int((v - min_v) / bucket_size), bins - 1)
377
+ buckets[idx] += 1
378
+
379
+ max_count = max(buckets)
380
+ lines = []
381
+ lines.append(f" {bold('直方图')}")
382
+ lines.append(f" {dim(f'区间数: {bins}, 总值: {len(values)}')}")
383
+ lines.append("")
384
+
385
+ for i in range(bins):
386
+ lo = min_v + i * bucket_size
387
+ hi = lo + bucket_size
388
+ count = buckets[i]
389
+ bar_len = int(count / max_count * width) if max_count > 0 else 0
390
+ bar = BAR_FULL * bar_len
391
+
392
+ if i == 0:
393
+ label = f"{lo:.1f}-{hi:.1f}"
394
+ elif i == bins - 1:
395
+ label = f"{lo:.1f}+"
396
+ else:
397
+ label = f"{lo:.1f}"
398
+
399
+ pct = count / len(values) * 100
400
+ lines.append(f" {dim(label):<16} {green(bar)} {count} ({pct:.1f}%)")
401
+
402
+ return '\n'.join(lines)
403
+
404
+
405
+ def main():
406
+ import argparse
407
+ parser = argparse.ArgumentParser(description='终端图表生成器')
408
+ parser.add_argument('type', choices=['bar', 'line', 'pie', 'hist'],
409
+ help='图表类型')
410
+ parser.add_argument('data', nargs='?', help='数据(JSON 或 key:value 格式,或从 stdin 读取)')
411
+ parser.add_argument('-w', '--width', type=int, default=40, help='图表宽度')
412
+ parser.add_argument('-H', '--height', type=int, default=12, help='图表高度')
413
+ parser.add_argument('--horizontal', action='store_true', help='条形图水平模式')
414
+ parser.add_argument('--sort', action='store_true', help='排序(条形图)')
415
+ parser.add_argument('--no-values', action='store_true', help='隐藏数值')
416
+ args = parser.parse_args()
417
+
418
+ # Read data
419
+ data_source = args.data
420
+ if not data_source and not sys.stdin.isatty():
421
+ data_source = sys.stdin.read()
422
+
423
+ if not data_source:
424
+ print("错误: 请提供数据(参数或 stdin)")
425
+ sys.exit(1)
426
+
427
+ data = parse_data(data_source)
428
+ if not data:
429
+ print("错误: 无法解析数据。支持 JSON 或 key:value 格式")
430
+ sys.exit(1)
431
+
432
+ if args.type == 'bar':
433
+ print(draw_bar_chart(data, args.width, args.height,
434
+ args.horizontal, args.sort, not args.no_values))
435
+ elif args.type == 'line':
436
+ print(draw_line_chart(data, args.width, args.height))
437
+ elif args.type == 'pie':
438
+ draw_pie_chart(data, args.radius if hasattr(args, 'radius') else 8)
439
+ elif args.type == 'hist':
440
+ print(draw_histogram_chart(data, 10, args.width))
441
+
442
+
443
+ if __name__ == '__main__':
444
+ main()
@@ -0,0 +1,3 @@
1
+ """CLI entry point for `python -m chart_cli`"""
2
+ from chart_cli import main
3
+ main()
@@ -0,0 +1,5 @@
1
+ from evolver_tools.vendor.colors.__main__ import (
2
+ hex_to_rgb, rgb_to_hex, hsl_to_rgb, show_256, show_basic, main
3
+ )
4
+
5
+ __all__ = ['hex_to_rgb', 'rgb_to_hex', 'hsl_to_rgb', 'show_256', 'show_basic', 'main']
@@ -0,0 +1,97 @@
1
+
2
+ import sys
3
+ import re
4
+
5
+ def hex_to_rgb(h):
6
+ h = h.lstrip('#')
7
+ if len(h) == 3:
8
+ h = ''.join(c*2 for c in h)
9
+ return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))
10
+
11
+ def rgb_to_hex(r, g, b):
12
+ return f'#{r:02x}{g:02x}{b:02x}'
13
+
14
+ def hsl_to_rgb(h, s, l):
15
+ h = h / 360.0
16
+ s = s / 100.0
17
+ l = l / 100.0
18
+ def hue2rgb(p, q, t):
19
+ if t < 0: t += 1
20
+ if t > 1: t -= 1
21
+ if t < 1/6: return p + (q - p) * 6 * t
22
+ if t < 1/2: return q
23
+ if t < 2/3: return p + (q - p) * (2/3 - t) * 6
24
+ return p
25
+ if s == 0:
26
+ r = g = b = l
27
+ else:
28
+ q = l * (1 + s) if l < 0.5 else l + s - l * s
29
+ p = 2 * l - q
30
+ r = hue2rgb(p, q, h + 1/3)
31
+ g = hue2rgb(p, q, h)
32
+ b = hue2rgb(p, q, h - 1/3)
33
+ return (round(r*255), round(g*255), round(b*255))
34
+
35
+ def show_256():
36
+ print("\n 256-color chart (bg):")
37
+ for i in range(0, 16, 8):
38
+ row = ''.join(f'\x1b[48;5;{i+j}m {i+j:>3} \x1b[0m' for j in range(8))
39
+ print(f' {row}')
40
+ for block in range(16, 232, 36):
41
+ print()
42
+ for i in range(block, block+36, 6):
43
+ row = ''.join(f'\x1b[48;5;{i+j}m {i+j:>3} \x1b[0m' for j in range(6) if i+j < 232)
44
+ print(f' {row}')
45
+ print()
46
+ print(" Grayscale (232-255):")
47
+ row = ''.join(f'\x1b[48;5;{i}m {i:>3} \x1b[0m' for i in range(232, 256))
48
+ print(f' {row}')
49
+ print('\x1b[0m')
50
+
51
+ def show_basic():
52
+ print("\n Basic 16 colors:")
53
+ for i in range(8):
54
+ fg = f'\x1b[38;5;{i}m'
55
+ bg = f'\x1b[48;5;{i}m'
56
+ bright = f'\x1b[38;5;{i+8}m'
57
+ print(f' {fg}Color {i}\x1b[0m {bright}Color {i+8}\x1b[0m {bg} \x1b[0m')
58
+
59
+ def main():
60
+ args = sys.argv[1:]
61
+ if not args:
62
+ show_basic()
63
+ show_256()
64
+ return
65
+
66
+ cmd = args[0]
67
+
68
+ if cmd == "256":
69
+ show_256()
70
+ elif cmd == "basic":
71
+ show_basic()
72
+ elif cmd in ("hex", "h") and len(args) >= 2:
73
+ try:
74
+ r, g, b = hex_to_rgb(args[1])
75
+ h, s, v = 0, 0, 0
76
+ print(f"HEX: {args[1].lower()}")
77
+ print(f"RGB: {r}, {g}, {b}")
78
+ print(f"\x1b[48;2;{r};{g};{b}m Sample \x1b[0m")
79
+ except:
80
+ print("Usage: colors hex #ff6600")
81
+ elif cmd in ("rgb", "r") and len(args) >= 4:
82
+ try:
83
+ r, g, b = int(args[1]), int(args[2]), int(args[3])
84
+ print(f"RGB: {r}, {g}, {b}")
85
+ print(f"HEX: {rgb_to_hex(r, g, b)}")
86
+ print(f"\x1b[48;2;{r};{g};{b}m Sample \x1b[0m")
87
+ except:
88
+ print("Usage: colors rgb 255 102 0")
89
+ else:
90
+ print("Usage: colors [hex|rgb|256|basic] [args...]")
91
+ print(" colors — show all color charts")
92
+ print(" colors 256 — 256-color chart")
93
+ print(" colors hex #ff0 — convert hex to RGB with preview")
94
+ print(" colors rgb 255 102 0 — convert RGB to hex with preview")
95
+
96
+ if __name__ == "__main__":
97
+ main()
@@ -0,0 +1,5 @@
1
+ """csv-stats — Zero-dependency CSV data analysis tool."""
2
+
3
+ __version__ = "1.0.0"
4
+ __author__ = "Evolver"
5
+ __license__ = "MIT"
@@ -0,0 +1,4 @@
1
+ """Allow `python -m csv_stats` to work."""
2
+ from csv_stats.cli import main
3
+
4
+ main()