Repository navigation
Expand file tree
/
Copy pathshellmbti.py
More file actions
277 lines (253 loc) ยท 13.5 KB
/
Copy pathshellmbti.py
File metadata and controls
277 lines (253 loc) ยท 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
shellmbti v0.1 โ ไฝ ็็ป็ซฏๅๅฒ๏ผๆด้ฒไบไฝ ๆฏ่ฐ
่ฏปๅๆฌๅฐ shell ๅๅฒ(zsh/bash)๏ผ็ฆป็บฟๅๆ โ ็ป็ซฏ MBTI ไบบๆ ผ + ๅไบซๅก็
้ถไพ่ต(python3 stdlib) ยท ๆฐๆฎไธๅบๆฌๆบ
"""
import os, sys, re, json, argparse, webbrowser
# ---------- ้ข่ฒ ----------
class C:
G="\033[32m"; Y="\033[33m"; R="\033[31m"; B="\033[36m"; Bd="\033[1m"; D="\033[2m"; _="\033[0m"
def out(s): print(s)
# ---------- 1. ๅฎไฝๅนถ่งฃๆๅๅฒ ----------
def find_history():
cands = []
env = os.environ.get("HISTFILE")
if env: cands.append(os.path.expanduser(env))
cands += [os.path.expanduser(p) for p in ("~/.zsh_history","~/.bash_history","~/.local/share/fish/fish_history")]
for p in cands:
if p and os.path.isfile(p) and os.path.getsize(p) > 0:
return p
return None
def parse(path):
"""่ฟๅๅฝไปคๅ่กจ(ๅนฒๅ็ๅฝไปคๅญ็ฌฆไธฒ) + ๆฏๅฆๆๆถ้ดๆณ"""
cmds, has_ts = [], False
zsh_meta = re.compile(r"^: (\d+):\d+;(.*)$", re.S)
try:
raw = open(path, encoding="utf-8", errors="ignore").read()
except Exception as e:
sys.exit(f"่ฏปๅๅคฑ่ดฅ: {e}")
lines = raw.split("\n")
if path.endswith("fish_history"):
cmds = [l[7:] for l in lines if l.startswith("- cmd: ")]
return [c.strip() for c in cmds if c.strip()], False
cur, cur_ts = None, None
for ln in lines:
if not ln.strip(): continue
m = zsh_meta.match(ln)
if m:
if cur is not None: cmds.append(cur)
cur, cur_ts = m.group(2), int(m.group(1)); has_ts = True
else:
# bash ๆฎ้่ก ๆ zsh ๅค่ก็ปญ่ก
if cur is not None and has_ts:
cur += "\n" + ln # zsh ๅค่ก็ปญ่ก
else:
if cur is not None: cmds.append(cur)
cur, cur_ts = ln, None
if cur is not None: cmds.append(cur)
return [c.strip() for c in cmds if c.strip()], has_ts
# ---------- 2. ๅๆ ----------
CATS = {
"git": ["git"],
"ๅฎนๅจไบ": ["docker","kubectl","helm","podman","docker-compose","k9s"],
"่ฟ็จ": ["ssh","scp","sftp","mosh","telnet"],
"็ฝ็ป": ["curl","wget","ping","dig","nc","netstat","ss","traceroute","iftop"],
"็ผ่พ": ["vim","vi","nvim","nano","code","emacs","subl"],
"็ณป็ป": ["sudo","chmod","chown","systemctl","launchctl","kill","killall","top","htop","df","du","ps","mount"],
"ๅ
็ฎก็":["brew","apt","apt-get","yum","dnf","pip","pip3","npm","pnpm","yarn","cargo","gem","go"],
"ๆไปถ": ["ls","cd","cp","mv","rm","mkdir","touch","cat","head","tail","less","find","grep","rg","tar","unzip","open","eza"],
"่ๆฌ": ["bash","sh","zsh","python","python3","node","make","./"],
}
NIGHT = ["vim","nvim","ssh","tail","grep","docker","curl","python3","git commit","git push"]
CREATIVE = ["vim","vi","nvim","nano","code","echo","touch","mkdir","git commit","git init","npm init","cargo new","python3","node","make"]
AUTOMATION = ["bash","sh","chmod","make","source","crontab","alias","python3","./","docker-compose"]
def first_token(c):
t = c.strip().split()
t0 = t[0] if t else ""
return t0.split("/")[-1]
def analyze(cmds, has_ts, ts_list):
total = len(cmds)
firsts = [first_token(c) for c in cmds]
uniq = set(firsts)
diversity = len(uniq)/total if total else 0
from collections import Counter
fc = Counter(firsts)
top10 = fc.most_common(10)
top_share = sum(n for _,n in top10)/total if total else 0
# ๅ็ฑป่ฎกๆฐ(ไธๆกๅฝไปคๅฏไธญๅค็ฑป)
joined = "\n".join(cmds).lower()
cat_hits = {}
for cat, keys in CATS.items():
cat_hits[cat] = sum(joined.count(" "+k+" ")+joined.count("\n"+k+" ")+ (1 if joined.startswith(k+" ") else 0) for k in keys)
# ๅค่ก(ไป
ๅฝๆๆถ้ดๆณ)
night = None
if has_ts and ts_list:
import datetime
hrs = [datetime.datetime.fromtimestamp(t).hour for t in ts_list if t]
if hrs: night = round(100*sum(1 for h in hrs if h >= 0 and h < 6)/len(hrs))
longest = max(cmds, key=len) if cmds else ""
sudo_n = sum(1 for c in cmds if c.startswith("sudo "))
git_n = sum(1 for c in cmds if c.startswith("git "))
creative_n = sum(1 for c in cmds if any(c.lower().startswith(k) for k in CREATIVE))
auto_n = sum(1 for c in cmds if any(c.lower().startswith(k) for k in AUTOMATION))
sysops_n = sum(1 for c in cmds if any(first_token(c) in CATS["ๆไปถ"] for _ in [0]))
# MBTI ๅ็ปด
d1 = "E" if diversity >= 0.30 else "I" # ๆข็ดขๅนฟๅบฆ
d2 = "N" if total and creative_n/total >= 0.22 else "S" # ๅ้ vs ๆไฝ
d3 = "T" if total and auto_n/total >= 0.05 else "F" # ่ชๅจๅ vs ๆๅจ
d4 = "J" if top_share <= 0.55 else "P" # ๅคๆ ท่ๅฅ vs ไน ๆฏ้ๅค
mbti = d1+d2+d3+d4
return dict(total=total, unique=len(uniq), diversity=round(diversity*100),
top10=top10, top_share=round(top_share*100), cat=cat_hits,
night=night, longest=longest, longest_len=len(longest),
sudo=sudo_n, git=git_n, mbti=mbti)
# ---------- 3. ไบบๆ ผๅบ ----------
ARCH = {
"INTP":"ๆทฑๅค็ผ้ๆฏๅฃซ","INTJ":"ๆฒ้ป็ๆถๆๅธ","INFP":"ๆ่
พๆไบบ","INFJ":"้ๅฝข็ๅฎๆค่
",
"ENTP":"ไธ็ฉ็ๅฏ่ๆฌ","ENTJ":"่ชๅจๅ็ไบบ","ENFP":"ๅฅฝๅฅๅฟๆฐธๅจๆบ","ENFJ":"ๅ
จ็ปๅทฅๅ
ทไบบ",
"ISTP":"ไธๆฌกๆงๆนๆกๅคงๅธ","ISTJ":"ๅฎๆถไปปๅกๆ็ฒพ","ISFP":"ๆ็ฎไธปไน่
","ISFJ":"ๆ ๅๅฎๅคไบบ",
"ESTP":"ๅ
่ทไบๅ่ฏด","ESTJ":"ๆต็จ้้ข่ญฆ","ESFP":"ๆๅๅ็ซๆ็","ESFJ":"ไบบๅฝข็พ็งๅ
จไนฆ",
}
DESC = {
"I":"ๆทฑ่ๆบๅจๅ
้จ","E":"ๅคฉๅคฉๅพๅคๆข", "N":"็ฑ้ ๆฐไธ่ฅฟ","S":"็จณๆ็จณๆๅนฒๆไฝ",
"T":"่ฝ่ชๅจ็ปไธๆ็น","F":"ไธๆกๆกๆๆฒไนๅพ็ฝ", "J":"่ๅฅๅคๆ ท็ฑๅฐ้ฒ","P":"่่่ฎฐๅฟ่ตฐๅคฉไธ",
}
def persona_line(m):
d1,d2,d3,d4 = m
return f"{d1}={DESC[d1]} ยท {d2}={DESC[d2]} ยท {d3}={DESC[d3]} ยท {d4}={DESC[d4]}"
# ---------- 4. ่พๅบ ----------
def term_report(st, histfile):
m = st["mbti"]
out = []
out.append("")
out.append(f"{C.B} โโโโโโโโ โโโ โโโโโโโโโโโโโโ โโโ โโโโ โโโโโโโโโโโ โโโโโโโ โโโ{C._}")
out.append(f"{C.B} โโโโโโโโ โโโ โโโโโโโโโโโโโโ โโโ โโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ{C._}")
out.append(f"{C.G} โโโโโโโโ โโโโโโโโโโโโโโ โโโ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{C._}")
out.append(f"{C.G} โโโโโโโโ โโโโโโโโโโโโโโ โโโ โโโ โโโโโโโโโโโโโโโโโโ โโโโโโโ โโโ{C._}")
out.append(f"{C.G} โโโโโโโโ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโ โโโโโโ โโโ โโโโโโโโ{C._}")
out.append(f"{C._} โโโโโโโโ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโ โโโ โโโโโโโโ{C._}")
out.append("")
out.append(f" {C.B}ไฝ ๆฏ {C.G}{m[0]}{C.Y}{m[1]}{C.B}{m[2]}{C.R}{m[3]}{C._} {C.B}โโ {C.G}{ARCH[m]}{C._}")
out.append(f" {C.D}{persona_line(m)}{C._}")
out.append("")
out.append(f" {C.B}๐ ไฝ ็็ป็ซฏๆฐๆฎ{C._} (ๆฅๆบ: {histfile})")
out.append(f" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")
out.append(f" ๆปๅฝไปคๆฐ {C.B}{st['total']:,}{C._} ๆก")
out.append(f" ๆข็ดขๆๆฐ {C.B}{st['diversity']}%{C._} ({st['unique']} ็งไธๅๅฝไปค)")
out.append(f" ไน ๆฏๆตๅบฆ {C.B}{st['top_share']}%{C._} (ๆๅธธ็จ10ๆกๅฝไปค็ๅ ๆฏ)")
if st["night"] is not None:
col = C.R if st["night"] >= 25 else C.G
out.append(f" ๅค่กๆตๅบฆ {col}{st['night']}%{C._} ็ๅฝไปคๅ็ๅจ 0-6 ็น")
else:
out.append(f" ๅค่กๆตๅบฆ {C.D}ๆ ๆณๅคๆญ(ๅๅฒๆ ๆถ้ดๆณ){C._}")
out.append(f" gitๆตๅบฆ {C.B}{st['git']:,}{C._} ๆก")
out.append(f" sudoๆฌกๆฐ {C.Y}{st['sudo']:,}{C._} ๆฌก (ๆๅ่ถๅคง่ดฃไปป่ถๅคง)")
lc = st["longest"]
if lc:
prev = lc if len(lc) <= 90 else lc[:87]+"..."
out.append(f" ๆ้ฟไธๆกๅฝไปค {C.B}{st['longest_len']}{C._} ๅญ็ฌฆ:")
out.append(f" {C.D} {prev}{C._}")
out.append("")
out.append(f" {C.B}๐ท ๅ็ฑป็ปๅ{C._}")
cats = sorted(st["cat"].items(), key=lambda x:-x[1])[:5]
mx = max([v for _,v in cats] or [1]) or 1
for name,v in cats:
bar = "โ"*max(1,int(v/mx*20))
out.append(f" {name:<6} {C.G}{bar}{C._} {v}")
out.append("")
out.append(f" {C.Y}โ ็ๆๅไบซๅก็: python3 shellmbti.py --card{C._}")
out.append("")
print("\n".join(out))
# ---------- 5. ๅไบซๅก็ ----------
CARD_TPL = """<!DOCTYPE html><html lang="zh"><head><meta charset="utf-8"><title>shellmbti</title></head>
<body style="margin:0;background:#0b0e14;display:flex;flex-direction:column;align-items:center;gap:16px;padding:24px;">
<canvas id="c" width="1080" height="1440"></canvas>
<button onclick="dl()" style="background:#2da44e;color:#04260a;border:none;border-radius:99px;padding:14px 44px;font-size:20px;font-weight:900;cursor:pointer;">โฌ๏ธ ไฟๅญๅก็ๅพ็</button>
<p style="color:#8b949e;font-size:13px;">ๆชๅพๆ็นๆ้ฎไฟๅญ โ ๅๅฐ็บขไนฆ/ๆๅๅ/็พค</p>
<script>
const S = __DATA__;
const c = document.getElementById('c'), x = c.getContext('2d');
x.fillStyle = '#0b0e14'; x.fillRect(0,0,1080,1440);
// ่พนๆก
x.strokeStyle = '#30363d'; x.lineWidth = 3; x.strokeRect(28,28,1024,1384);
// ้กถ้จ
x.fillStyle = '#2da44e'; x.font = 'bold 34px sans-serif'; x.textAlign='left';
x.fillText('โจ SHELL MBTI', 64, 96);
x.fillStyle = '#8b949e'; x.font = '24px sans-serif'; x.textAlign='right';
x.fillText('ไฝ ็็ป็ซฏ๏ผๆด้ฒไบไฝ ๆฏ่ฐ', 1016, 96);
// ๅคงๅญๆฏ
const L = S.mbti.split(''), cols = ['#58a6ff','#d2a8ff','#3fb950','#f0883e'];
L.forEach((ch,i)=>{
x.fillStyle = cols[i]; x.font = '900 190px -apple-system,"PingFang SC",sans-serif';
x.fillText(ch, 90 + i*230, 400);
});
// ไบบๆ ผๅ
x.fillStyle = '#ffffff'; x.font = 'bold 56px "PingFang SC",sans-serif'; x.textAlign='left';
x.fillText('__ARCH__', 64, 540);
x.fillStyle = '#8b949e'; x.font = '26px sans-serif';
x.fillText(S.persona, 64, 590);
// ๆฐๆฎๆก
const rows = [
['ๆปๅฝไปคๆฐ', S.total.toLocaleString()],
['ๆข็ดขๆๆฐ', S.diversity + '%'],
['ไน ๆฏๆตๅบฆ', S.top_share + '%'],
['ๅค่กๆตๅบฆ', S.night===null ? 'ๆช็ฅ' : S.night + '%'],
['git ๅฝไปค', String(S.git)],
['sudo ๆฌกๆฐ', String(S.sudo)],
];
rows.forEach((r,i)=>{
const y = 690 + i*92;
x.fillStyle = '#161b22'; x.fillRect(64, y-40, 952, 72);
x.fillStyle = '#e6edf3'; x.font = 'bold 30px "PingFang SC",sans-serif'; x.textAlign='left';
x.fillText(r[0], 92, y+8);
x.fillStyle = '#3fb950'; x.font = '900 38px sans-serif'; x.textAlign='right';
x.fillText(r[1], 986, y+10); x.textAlign='left';
});
// ๆ้ฟๅฝไปคๅฝฉ่
x.fillStyle = '#8b949e'; x.font = '22px sans-serif';
x.fillText('ๆ้ฟไธๆกๅฝไปค ' + S.longest_len + ' ๅญ็ฌฆ โโ ไฝ ๅฐๅบๅจๅไปไน?!', 64, 1300);
x.fillStyle = '#58a6ff'; x.font = 'bold 26px sans-serif';
x.fillText('๐ ๆใๅฐ่
่
ใ็ๆดๅค', 64, 1352);
x.textAlign='right'; x.fillStyle = '#8b949e'; x.font = '22px sans-serif';
x.fillText('ๆฐๆฎ 100% ๆฌๅฐๅๆ ยท shellmbti', 1016, 1352);
function dl(){ const a=document.createElement('a'); a.download='shellmbti.png'; a.href=c.toDataURL('image/png'); a.click(); }
</script></body></html>"""
# ---------- 6. main ----------
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--card", action="store_true", help="็ๆๅไบซๅก็ HTML")
ap.add_argument("--json", action="store_true", help="่พๅบ JSON")
a = ap.parse_args()
hist = find_history()
if not hist:
sys.exit("ๆฒกๆพๅฐ shell ๅๅฒๆไปถ (~/.zsh_history / ~/.bash_history)ใๅ
้ไพฟๆฒๅ ๆกๅฝไปคๅๆฅใ")
cmds, has_ts = parse(hist)
if len(cmds) < 10:
sys.exit(f"ๅๅฒๅชๆ {len(cmds)} ๆกๅฝไปค๏ผๅคชๅฐไบโโๅ
ๆญฃๅธธ็จๅ ๅคฉ็ป็ซฏๅๆฅ(่ณๅฐ10ๆก)ใ")
import datetime
ts_list = []
if has_ts:
for ln in open(hist, encoding="utf-8", errors="ignore"):
m = re.match(r"^: (\d+):\d+;", ln)
if m: ts_list.append(int(m.group(1)))
st = analyze(cmds, has_ts, ts_list)
st["mbti"] = analyze(cmds, has_ts, ts_list)["mbti"] # ๅทฒๅซ
if a.json:
print(json.dumps(st, ensure_ascii=False, indent=2)); return
if a.card:
arch = ARCH[st["mbti"]]
data = dict(mbti=st["mbti"], arch=arch, persona=persona_line(st["mbti"]),
total=st["total"], diversity=st["diversity"], top_share=st["top_share"],
night=st["night"], git=st["git"], sudo=st["sudo"], longest_len=st["longest_len"])
html = CARD_TPL.replace("__DATA__", json.dumps(data, ensure_ascii=False)).replace("__ARCH__", arch)
out_path = "shellmbti-card.html"
open(out_path, "w", encoding="utf-8").write(html)
try: webbrowser.open("file://" + os.path.abspath(out_path))
except Exception: pass
print(f"ๅก็ๅทฒ็ๆ: {os.path.abspath(out_path)} (ๅทฒๅจๆต่งๅจๆๅผ, ็นๆ้ฎไฟๅญๅพ็)")
return
term_report(st, hist)
if __name__ == "__main__":
main()