git-analytics-cli 0.1.0__tar.gz → 0.1.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-analytics-cli
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Local-first Git habit analytics — scan your repos, get a developer persona and health report
5
5
  Author: Git Analytics Contributors
6
6
  License-Expression: MIT
@@ -22,8 +22,7 @@ Classifier: Topic :: Software Development :: Version Control :: Git
22
22
  Requires-Python: >=3.8
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Provides-Extra: share-card
26
- Requires-Dist: Pillow>=8.0; extra == "share-card"
25
+ Requires-Dist: Pillow>=8.0
27
26
  Dynamic: license-file
28
27
 
29
28
  # Git Analytics
@@ -35,13 +34,7 @@ Dynamic: license-file
35
34
  ## 安装
36
35
 
37
36
  ```bash
38
- pip install git-analytics
39
- ```
40
-
41
- 如需生成 PNG 分享卡片(可选,需要 Pillow):
42
-
43
- ```bash
44
- pip install git-analytics[share-card]
37
+ pip install git-analytics-cli
45
38
  ```
46
39
 
47
40
  ## 30 秒快速开始
@@ -123,11 +116,13 @@ git-analytics ~/Projects --max-depth 5
123
116
 
124
117
  ## 分享卡片
125
118
 
126
- 使用 `--share-card` 生成一个 HTML 设计器,可以在浏览器中:
119
+ 运行 `git-analytics --share-card` 生成 `share-card.html`,浏览器打开后可预览、选主题、导出 PNG。
127
120
 
128
- - 预览你的开发者画像卡片
129
- - 选择配色主题
130
- - 导出 PNG 图片用于社交分享
121
+ 也可以命令行直接生成 PNG:
122
+
123
+ ```bash
124
+ python3 -m gen_share_card --data data.json --output card.png
125
+ ```
131
126
 
132
127
  ## 为什么不是全盘扫描
133
128
 
@@ -7,13 +7,7 @@
7
7
  ## 安装
8
8
 
9
9
  ```bash
10
- pip install git-analytics
11
- ```
12
-
13
- 如需生成 PNG 分享卡片(可选,需要 Pillow):
14
-
15
- ```bash
16
- pip install git-analytics[share-card]
10
+ pip install git-analytics-cli
17
11
  ```
18
12
 
19
13
  ## 30 秒快速开始
@@ -95,11 +89,13 @@ git-analytics ~/Projects --max-depth 5
95
89
 
96
90
  ## 分享卡片
97
91
 
98
- 使用 `--share-card` 生成一个 HTML 设计器,可以在浏览器中:
92
+ 运行 `git-analytics --share-card` 生成 `share-card.html`,浏览器打开后可预览、选主题、导出 PNG。
99
93
 
100
- - 预览你的开发者画像卡片
101
- - 选择配色主题
102
- - 导出 PNG 图片用于社交分享
94
+ 也可以命令行直接生成 PNG:
95
+
96
+ ```bash
97
+ python3 -m gen_share_card --data data.json --output card.png
98
+ ```
103
99
 
104
100
  ## 为什么不是全盘扫描
105
101
 
@@ -0,0 +1,369 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Git Analytics - 分享卡片生成器
4
+ 从 data.json 读取数据,生成可分享的代码画像图片
5
+ """
6
+
7
+ import json
8
+ import argparse
9
+ import os
10
+ import sys
11
+ from datetime import datetime
12
+
13
+ try:
14
+ from PIL import Image, ImageDraw, ImageFont
15
+ except ImportError:
16
+ print("需要安装 Pillow 才能生成 PNG 分享卡片。")
17
+ print("运行: pip install git-analytics-cli[share-card]")
18
+ sys.exit(1)
19
+
20
+
21
+ # --- 默认配色方案 ---
22
+ THEMES = {
23
+ 'default': {
24
+ 'bg_top': (15, 12, 41), # 深紫
25
+ 'bg_bot': (36, 36, 62), # 深蓝紫
26
+ 'accent1': (167, 139, 250), # 紫
27
+ 'accent2': (96, 165, 250), # 蓝
28
+ 'gold': (251, 191, 36), # 金色
29
+ },
30
+ 'ocean': {
31
+ 'bg_top': (10, 25, 49),
32
+ 'bg_bot': (15, 50, 80),
33
+ 'accent1': (72, 209, 204),
34
+ 'accent2': (100, 181, 246),
35
+ 'gold': (255, 214, 110),
36
+ },
37
+ 'forest': {
38
+ 'bg_top': (15, 32, 20),
39
+ 'bg_bot': (25, 55, 35),
40
+ 'accent1': (129, 199, 132),
41
+ 'accent2': (165, 214, 167),
42
+ 'gold': (255, 213, 79),
43
+ },
44
+ 'sunset': {
45
+ 'bg_top': (45, 10, 25),
46
+ 'bg_bot': (75, 20, 45),
47
+ 'accent1': (255, 138, 101),
48
+ 'accent2': (255, 183, 77),
49
+ 'gold': (255, 235, 59),
50
+ },
51
+ 'minimal': {
52
+ 'bg_top': (30, 30, 30),
53
+ 'bg_bot': (50, 50, 50),
54
+ 'accent1': (200, 200, 200),
55
+ 'accent2': (160, 160, 160),
56
+ 'gold': (255, 255, 255),
57
+ },
58
+ }
59
+
60
+
61
+ def load_font(size):
62
+ """加载中文字体"""
63
+ paths = [
64
+ "/System/Library/Fonts/PingFang.ttc",
65
+ "/System/Library/Fonts/STHeiti Medium.ttc",
66
+ "/System/Library/Fonts/Hiragino Sans GB.ttc",
67
+ ]
68
+ for p in paths:
69
+ try:
70
+ return ImageFont.truetype(p, size)
71
+ except Exception:
72
+ continue
73
+ return ImageFont.load_default()
74
+
75
+
76
+ def load_data(data_path):
77
+ """加载分析数据"""
78
+ with open(data_path, 'r', encoding='utf-8') as f:
79
+ return json.load(f)
80
+
81
+
82
+ def get_date_range(data, since=None, until=None):
83
+ """获取时间范围文本"""
84
+ if since and until:
85
+ return f"{since} ~ {until}"
86
+
87
+ # 从数据中提取
88
+ projects = data.get('projects', [])
89
+ all_first = []
90
+ all_last = []
91
+ for p in projects:
92
+ if p.get('first_commit'):
93
+ all_first.append(p['first_commit'])
94
+ if p.get('last_commit'):
95
+ all_last.append(p['last_commit'])
96
+
97
+ if all_first and all_last:
98
+ start = min(all_first)
99
+ end = max(all_last)
100
+ # 格式化为简短日期
101
+ start_fmt = datetime.strptime(start, '%Y-%m-%d').strftime('%Y.%m.%d')
102
+ end_fmt = datetime.strptime(end, '%Y-%m-%d').strftime('%Y.%m.%d')
103
+ return f"{start_fmt} ~ {end_fmt}"
104
+
105
+ return "全部时间"
106
+
107
+
108
+ def draw_card(data, theme_colors, output_path, date_range=None):
109
+ """绘制分享卡片"""
110
+ W, H = 420, 720
111
+
112
+ # 解包颜色
113
+ bg_top = theme_colors['bg_top']
114
+ bg_bot = theme_colors['bg_bot']
115
+ accent1 = theme_colors['accent1']
116
+ accent2 = theme_colors['accent2']
117
+ gold = theme_colors['gold']
118
+
119
+ white = (255, 255, 255)
120
+ white_80 = (255, 255, 255, 204)
121
+ white_50 = (255, 255, 255, 128)
122
+ white_30 = (255, 255, 255, 77)
123
+ white_10 = (255, 255, 255, 26)
124
+ glass_bg = (255, 255, 255, 15)
125
+ glass_border = (255, 255, 255, 26)
126
+
127
+ # 提取数据
128
+ summary = data.get('summary', {})
129
+ persona = data.get('persona', {})
130
+ score = data.get('habit_score', {}).get('total', 0)
131
+ tags = data.get('developer_tags', [])
132
+ dimensions = persona.get('dimensions', {})
133
+
134
+ commits = str(summary.get('total_commits', 0))
135
+ active_days = str(summary.get('total_active_days', 0))
136
+ projects = str(summary.get('total_projects', 0))
137
+ persona_icon = persona.get('icon', '?')
138
+ persona_name = persona.get('name', '未知')
139
+ persona_code = persona.get('code', '??????')
140
+
141
+ # 维度标签
142
+ dim_list = []
143
+ dim_names = {
144
+ 'time': '时间偏好', 'rhythm': '节奏风格', 'focus': '专注程度',
145
+ 'style': '开发风格', 'engineering': '工程取向', 'ai': 'AI 协作'
146
+ }
147
+ dim_traits = {
148
+ 'time': {'D': '白天型', 'N': '夜猫型'},
149
+ 'rhythm': {'S': '冲刺型', 'M': '马拉松型'},
150
+ 'focus': {'C': '核心深挖', 'D': '多线并行'},
151
+ 'style': {'P': '功能开拓', 'G': '系统守护'},
152
+ 'engineering': {'Q': '质量洁癖', 'R': '快速迭代'},
153
+ 'ai': {'A': 'AI 搭子', 'H': '纯手工'},
154
+ }
155
+ dim_icons = {
156
+ 'time': '☀️', 'rhythm': '⚡', 'focus': '🎯',
157
+ 'style': '🚀', 'engineering': '✨', 'ai': '🤖'
158
+ }
159
+ for key in ['time', 'rhythm', 'focus', 'style', 'engineering', 'ai']:
160
+ dim = dimensions.get(key, {})
161
+ code = dim.get('code', '?')
162
+ trait = dim_traits.get(key, {}).get(code, '未知')
163
+ icon = dim_icons.get(key, '?')
164
+ dim_list.append((icon, dim_names.get(key, key), trait))
165
+
166
+ # 标签药丸
167
+ tag_texts = []
168
+ for t in tags[1:6]: # 跳过主标签,取最多5个
169
+ icon = t.get('icon', '')
170
+ name = t.get('name', '')
171
+ tag_texts.append(f"{icon} {name}")
172
+
173
+ # 时间范围
174
+ if not date_range:
175
+ date_range = get_date_range(data)
176
+
177
+ # --- 加载字体 ---
178
+ font_title = load_font(18)
179
+ font_persona_name = load_font(32)
180
+ font_persona_code = load_font(14)
181
+ font_stat_val = load_font(30)
182
+ font_stat_label = load_font(11)
183
+ font_dim_label = load_font(9)
184
+ font_dim_val = load_font(11)
185
+ font_score_val = load_font(48)
186
+ font_score_label = load_font(12)
187
+ font_tag = load_font(11)
188
+ font_footer = load_font(10)
189
+ font_date = load_font(11)
190
+
191
+ # --- 创建画布 ---
192
+ img = Image.new("RGBA", (W, H), bg_top)
193
+ draw = ImageDraw.Draw(img)
194
+
195
+ # 渐变背景
196
+ for y in range(H):
197
+ ratio = y / H
198
+ r = int(bg_top[0] + (bg_bot[0] - bg_top[0]) * ratio)
199
+ g = int(bg_top[1] + (bg_bot[1] - bg_top[1]) * ratio)
200
+ b = int(bg_top[2] + (bg_bot[2] - bg_top[2]) * ratio)
201
+ draw.line([(0, y), (W, y)], fill=(r, g, b, 255))
202
+
203
+ # --- 辅助函数 ---
204
+ def center_text(text, font, y_pos, color=white):
205
+ bbox = draw.textbbox((0, 0), text, font=font)
206
+ tw = bbox[2] - bbox[0]
207
+ x = (W - tw) // 2
208
+ draw.text((x, y_pos), text, fill=color, font=font)
209
+
210
+ def draw_rounded_rect(xy, radius, fill=None, outline=None, width=1):
211
+ draw.rounded_rectangle(xy, radius=radius, fill=fill, outline=outline, width=width)
212
+
213
+ # --- 绘制内容 ---
214
+ pad = 36
215
+ y = 36
216
+
217
+ # 人格图标
218
+ center_text(persona_icon, font_persona_name, y, white)
219
+ y += 44
220
+
221
+ # 标题
222
+ center_text("我的代码画像", font_title, y, accent1)
223
+ y += 28
224
+
225
+ # 人格名称
226
+ center_text(persona_name, font_persona_name, y, white)
227
+ y += 42
228
+
229
+ # 代码
230
+ center_text(persona_code, font_persona_code, y, white_50)
231
+ y += 28
232
+
233
+ # --- 时间范围 ---
234
+ center_text(date_range, font_date, y, white_30)
235
+ y += 24
236
+
237
+ # --- 三个数据卡片 ---
238
+ stat_y = y
239
+ stat_w = (W - pad * 2 - 20) // 3
240
+ stat_items = [
241
+ (commits, "次提交"),
242
+ (active_days, "天活跃"),
243
+ (projects, "个项目"),
244
+ ]
245
+ for i, (val, label) in enumerate(stat_items):
246
+ sx = pad + i * (stat_w + 10)
247
+ draw_rounded_rect((sx, stat_y, sx + stat_w, stat_y + 80), radius=12, fill=glass_bg, outline=glass_border)
248
+ # 数值
249
+ bbox = draw.textbbox((0, 0), val, font=font_stat_val)
250
+ tw = bbox[2] - bbox[0]
251
+ draw.text((sx + (stat_w - tw) // 2, stat_y + 14), val, fill=accent2, font=font_stat_val)
252
+ # 标签
253
+ bbox = draw.textbbox((0, 0), label, font=font_stat_label)
254
+ tw = bbox[2] - bbox[0]
255
+ draw.text((sx + (stat_w - tw) // 2, stat_y + 52), label, fill=white_50, font=font_stat_label)
256
+
257
+ y = stat_y + 96
258
+
259
+ # --- 六个维度芯片 ---
260
+ dim_y = y
261
+ dim_w = (W - pad * 2 - 16) // 3
262
+ dim_h = 64
263
+ for i, (icon, label, val) in enumerate(dim_list):
264
+ col = i % 3
265
+ row = i // 3
266
+ dx = pad + col * (dim_w + 8)
267
+ dy = dim_y + row * (dim_h + 8)
268
+ draw_rounded_rect((dx, dy, dx + dim_w, dy + dim_h), radius=10, fill=glass_bg, outline=glass_border)
269
+ # 标签
270
+ bbox = draw.textbbox((0, 0), label, font=font_dim_label)
271
+ tw = bbox[2] - bbox[0]
272
+ draw.text((dx + (dim_w - tw) // 2, dy + 10), label, fill=white_30, font=font_dim_label)
273
+ # 值
274
+ bbox = draw.textbbox((0, 0), val, font=font_dim_val)
275
+ tw = bbox[2] - bbox[0]
276
+ draw.text((dx + (dim_w - tw) // 2, dy + 32), val, fill=white_80, font=font_dim_val)
277
+
278
+ y = dim_y + 2 * (dim_h + 8) + 16
279
+
280
+ # --- 分数条 ---
281
+ score_y = y
282
+ draw_rounded_rect((pad, score_y, W - pad, score_y + 80), radius=14, fill=glass_bg, outline=glass_border)
283
+
284
+ # 分数数字
285
+ draw.text((pad + 24, score_y + 12), str(score), fill=gold, font=font_score_val)
286
+
287
+ # 右侧
288
+ rx = pad + 100
289
+ draw.text((rx, score_y + 18), "习惯健康分 / 100", fill=white_50, font=font_score_label)
290
+
291
+ # 进度条
292
+ bar_y = score_y + 44
293
+ bar_w = W - pad * 2 - 120
294
+ bar_x = rx
295
+ draw_rounded_rect((bar_x, bar_y, bar_x + bar_w, bar_y + 6), radius=3, fill=white_10)
296
+ fill_w = int(bar_w * score / 100)
297
+ draw_rounded_rect((bar_x, bar_y, bar_x + fill_w, bar_y + 6), radius=3, fill=gold)
298
+
299
+ y = score_y + 96
300
+
301
+ # --- 标签药丸 ---
302
+ if tag_texts:
303
+ tag_y = y
304
+ tag_gap = 8
305
+ tag_widths = []
306
+ for t in tag_texts:
307
+ bbox = draw.textbbox((0, 0), t, font=font_tag)
308
+ tag_widths.append(bbox[2] - bbox[0] + 20)
309
+
310
+ total_w = sum(tag_widths) + tag_gap * (len(tag_texts) - 1)
311
+ tx = (W - total_w) // 2
312
+
313
+ for i, t in enumerate(tag_texts):
314
+ tw = tag_widths[i]
315
+ draw_rounded_rect((tx, tag_y, tx + tw, tag_y + 28), radius=14, fill=glass_bg, outline=glass_border)
316
+ draw.text((tx + 10, tag_y + 6), t, fill=white_80, font=font_tag)
317
+ tx += tw + tag_gap
318
+
319
+ y = tag_y + 44
320
+
321
+ # --- 分割线 ---
322
+ draw.line([(pad, y), (W - pad, y)], fill=white_10, width=1)
323
+ y += 16
324
+
325
+ # --- 底部品牌 ---
326
+ center_text("Git Analytics · 代码习惯体检", font_footer, y, white_30)
327
+
328
+ # --- 保存 ---
329
+ img = img.convert("RGB")
330
+ img.save(output_path, "PNG")
331
+ print(f"✅ 卡片已保存: {output_path}")
332
+
333
+
334
+ def main():
335
+ parser = argparse.ArgumentParser(description='生成代码画像分享卡片')
336
+ parser.add_argument('--data', default='data.json', help='数据文件路径 (默认: data.json)')
337
+ parser.add_argument('--output', default='share_card.png', help='输出图片路径 (默认: share_card.png)')
338
+ parser.add_argument('--theme', choices=list(THEMES.keys()), default='default',
339
+ help='配色主题 (默认: default)')
340
+ parser.add_argument('--since', help='起始日期 (如: 2024-01-01)')
341
+ parser.add_argument('--until', help='截止日期 (如: 2025-12-31)')
342
+ args = parser.parse_args()
343
+
344
+ # 加载数据
345
+ data_path = os.path.abspath(args.data)
346
+ if not os.path.exists(data_path):
347
+ print(f"❌ 数据文件不存在: {data_path}")
348
+ print("请先运行 git_analytics.py 生成数据")
349
+ return
350
+
351
+ data = load_data(data_path)
352
+
353
+ # 构建时间范围文本
354
+ date_range = None
355
+ if args.since or args.until:
356
+ since = args.since or '...'
357
+ until = args.until or '至今'
358
+ date_range = f"{since} ~ {until}"
359
+
360
+ # 获取主题颜色
361
+ theme_colors = THEMES[args.theme]
362
+
363
+ # 生成卡片
364
+ output_path = os.path.abspath(args.output)
365
+ draw_card(data, theme_colors, output_path, date_range)
366
+
367
+
368
+ if __name__ == '__main__':
369
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-analytics-cli
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Local-first Git habit analytics — scan your repos, get a developer persona and health report
5
5
  Author: Git Analytics Contributors
6
6
  License-Expression: MIT
@@ -22,8 +22,7 @@ Classifier: Topic :: Software Development :: Version Control :: Git
22
22
  Requires-Python: >=3.8
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Provides-Extra: share-card
26
- Requires-Dist: Pillow>=8.0; extra == "share-card"
25
+ Requires-Dist: Pillow>=8.0
27
26
  Dynamic: license-file
28
27
 
29
28
  # Git Analytics
@@ -35,13 +34,7 @@ Dynamic: license-file
35
34
  ## 安装
36
35
 
37
36
  ```bash
38
- pip install git-analytics
39
- ```
40
-
41
- 如需生成 PNG 分享卡片(可选,需要 Pillow):
42
-
43
- ```bash
44
- pip install git-analytics[share-card]
37
+ pip install git-analytics-cli
45
38
  ```
46
39
 
47
40
  ## 30 秒快速开始
@@ -123,11 +116,13 @@ git-analytics ~/Projects --max-depth 5
123
116
 
124
117
  ## 分享卡片
125
118
 
126
- 使用 `--share-card` 生成一个 HTML 设计器,可以在浏览器中:
119
+ 运行 `git-analytics --share-card` 生成 `share-card.html`,浏览器打开后可预览、选主题、导出 PNG。
127
120
 
128
- - 预览你的开发者画像卡片
129
- - 选择配色主题
130
- - 导出 PNG 图片用于社交分享
121
+ 也可以命令行直接生成 PNG:
122
+
123
+ ```bash
124
+ python3 -m gen_share_card --data data.json --output card.png
125
+ ```
131
126
 
132
127
  ## 为什么不是全盘扫描
133
128
 
@@ -1,6 +1,7 @@
1
1
  LICENSE
2
2
  MANIFEST.in
3
3
  README.md
4
+ gen_share_card.py
4
5
  generate_report.py
5
6
  git_analytics.py
6
7
  pyproject.toml
@@ -1,3 +1,4 @@
1
+ gen_share_card
1
2
  generate_report
2
3
  git_analytics
3
4
  run
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "git-analytics-cli"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "Local-first Git habit analytics — scan your repos, get a developer persona and health report"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -24,10 +24,7 @@ classifiers = [
24
24
  "Programming Language :: Python :: 3.13",
25
25
  "Topic :: Software Development :: Version Control :: Git",
26
26
  ]
27
- dependencies = []
28
-
29
- [project.optional-dependencies]
30
- share-card = ["Pillow>=8.0"]
27
+ dependencies = ["Pillow>=8.0"]
31
28
 
32
29
  [project.urls]
33
30
  Homepage = "https://github.com/mark-618/git-analytics"
@@ -38,7 +35,7 @@ Issues = "https://github.com/mark-618/git-analytics/issues"
38
35
  git-analytics = "run:main"
39
36
 
40
37
  [tool.setuptools]
41
- py-modules = ["run", "git_analytics", "generate_report"]
38
+ py-modules = ["run", "git_analytics", "generate_report", "gen_share_card"]
42
39
 
43
40
  [tool.setuptools.data-files]
44
41
  "share/git-analytics" = ["share-card.html"]
@@ -1,3 +0,0 @@
1
-
2
- [share-card]
3
- Pillow>=8.0