bcmd 0.6.8__py3-none-any.whl → 0.6.10__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.

Potentially problematic release.


This version of bcmd might be problematic. Click here for more details.

bcmd/common/func.py CHANGED
@@ -15,5 +15,5 @@ def checkPathOrNotExists(folder: Path):
15
15
 
16
16
  @contextmanager
17
17
  def useResources(name: str):
18
- with importlib.resources.path('bcmdx.resources', name) as target:
18
+ with importlib.resources.path('bcmd.resources', name) as target:
19
19
  yield target
bcmd/tasks/__init__.py CHANGED
@@ -3,6 +3,7 @@ from . import bin
3
3
  from . import code
4
4
  from . import crypto
5
5
  from . import debian
6
+ from . import docs
6
7
  from . import download
7
8
  from . import image
8
9
  from . import json
bcmd/tasks/docs.py ADDED
@@ -0,0 +1,185 @@
1
+ import asyncio
2
+ import datetime
3
+ import pickle
4
+ import tkinter as tk
5
+ from contextlib import asynccontextmanager
6
+ from pathlib import Path
7
+ from tkinter import messagebox
8
+ from typing import Any, Final
9
+ from uuid import uuid4
10
+
11
+ import paramiko
12
+ from beni import bcolor, bcrypto, bfile, bpath, brun, btask, bzip
13
+ from beni.bform import BForm
14
+ from beni.bfunc import syncCall
15
+ from typer import Argument
16
+
17
+ from bcmd.utils.utils import decryptFileIgnoreError
18
+
19
+ app: Final = btask.newSubApp('Vitepress 网站相关')
20
+ conf: dict[str, Any] = {}
21
+ isUpload: bool = False
22
+ password: str = ''
23
+ now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
24
+
25
+ projectPath = bpath.get('')
26
+ docsPath = bpath.get('')
27
+ vitepressDistPath = bpath.get('')
28
+ distPath = bpath.get('')
29
+ deployPath = bpath.get('')
30
+ zipFile = bpath.get('')
31
+
32
+
33
+ @app.command()
34
+ @syncCall
35
+ async def build(
36
+ path: Path = Argument(Path.cwd(), help='workspace 路径'),
37
+ ):
38
+ '发布'
39
+ with bpath.useTempPath(True) as tempPath:
40
+ await init(path)
41
+ await userInput()
42
+ await vitepressBuild(tempPath / 'site')
43
+ await makeDeploy(tempPath / 'deploy')
44
+ for file in bpath.listPath(tempPath):
45
+ await bzip.sevenZip(zipFile, file)
46
+ if isUpload:
47
+ await upload()
48
+ bcolor.printGreen('OK')
49
+
50
+
51
+ async def init(path: Path):
52
+ global projectPath, docsPath, vitepressDistPath, distPath, deployPath, conf, zipFile
53
+
54
+ # 初始化目录路径
55
+ projectPath = path
56
+ docsPath = projectPath / 'docs'
57
+ vitepressDistPath = docsPath / '.vitepress/dist'
58
+ distPath = projectPath / 'dist'
59
+ deployPath = projectPath / 'deploy'
60
+
61
+ # 清空打包用到的目录
62
+ bpath.remove(distPath)
63
+ bpath.make(distPath)
64
+ bpath.remove(vitepressDistPath)
65
+
66
+ # 更新配置
67
+ infoTomlFile = deployPath / 'info.toml'
68
+ if not infoTomlFile.exists():
69
+ btask.abort('部署文件不存在', infoTomlFile)
70
+ conf.update(await bfile.readToml(infoTomlFile))
71
+
72
+ # 整理特殊的字段
73
+ zipFile = distPath / f'{conf['domain']}_{now}.7z'
74
+ conf['upload_file_name'] = zipFile.name
75
+ conf['temp_path'] += f'/{uuid4()}'
76
+
77
+
78
+ async def userInput():
79
+ global isUpload, password
80
+
81
+ class BuildForm(BForm):
82
+ def __init__(self):
83
+ super().__init__(title='发布网站')
84
+ self.varIsUpload = tk.BooleanVar(value=True)
85
+ self.varPassword = tk.StringVar(value='')
86
+ self.addLabel('网站', conf['domain'])
87
+ self.addCheckBox('上传服务器', '', self.varIsUpload)
88
+ self.addEntry('请输入密码', self.varPassword, width=20, command=self.onBtn, password=True, focus=True)
89
+
90
+ def onBtn(self):
91
+ password = self.varPassword.get()
92
+ try:
93
+ conf['server_info'] = bcrypto.decryptJson(conf['server_info'], password)
94
+ self.destroy()
95
+ except:
96
+ messagebox.showerror('密码错误', '密码错误,请重新输入')
97
+
98
+ def getResult(self):
99
+ return self.varIsUpload.get(), self.varPassword.get()
100
+
101
+ isUpload, password = BuildForm().run()
102
+
103
+ # 配置里面每个字段都尝试解密(实际效率较低但不影响使用)
104
+ for k, v in conf.items():
105
+ try:
106
+ conf[k] = bcrypto.decryptText(v, password)
107
+ except:
108
+ pass
109
+
110
+
111
+ async def vitepressBuild(outputPath: Path):
112
+ with bpath.changePath(projectPath):
113
+ await brun.run('pnpm install')
114
+ await brun.run('pnpm docs:build')
115
+ bpath.copy(vitepressDistPath, outputPath)
116
+
117
+
118
+ async def makeDeploy(outputPath: Path):
119
+ bpath.copy(projectPath / 'deploy', outputPath)
120
+ bpath.remove(outputPath / 'info.toml')
121
+
122
+ # 删除配置里面加密和删除非字符串的配置,剩下的内容用于替换文件名和文件内容
123
+ dataDict: dict[str, Any] = pickle.loads(pickle.dumps(conf))
124
+ for k in list(dataDict.keys()):
125
+ v = dataDict[k]
126
+ if not isinstance(v, str):
127
+ del dataDict[k]
128
+
129
+ # 文件名以及内容调整
130
+ fileList = bpath.listFile(outputPath, True)
131
+ await asyncio.gather(*[decryptFileIgnoreError(x, password) for x in fileList])
132
+ for file in fileList:
133
+
134
+ # 替换文件名
135
+ toFile = str(file)
136
+ for k, v in dataDict.items():
137
+ toFile = toFile.replace(f'{{{k}}}', str(v))
138
+ if toFile != str(file):
139
+ file.rename(toFile)
140
+ file = bpath.get(toFile)
141
+
142
+ # 替换文件内容
143
+ content = await bfile.readText(file)
144
+ oldContent = content
145
+ for k, v in dataDict.items():
146
+ content = content.replace(f'{{{k}}}', str(v))
147
+ if oldContent != content:
148
+ await bfile.writeText(file, content)
149
+
150
+ # 将 Scrips 里面的文件都抽离到 dist 目录
151
+ scriptsPath = outputPath / 'Scripts'
152
+ for file in bpath.listFile(scriptsPath):
153
+ bpath.move(file, distPath / f'{conf['domain']}_{now}_{file.stem}{file.suffix}')
154
+ bpath.remove(scriptsPath)
155
+
156
+
157
+ @asynccontextmanager
158
+ async def sshClient():
159
+ client = paramiko.SSHClient()
160
+ client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
161
+ with bpath.useTempFile() as tempFile:
162
+ await bfile.writeText(tempFile, conf['server_key'])
163
+ client.connect(
164
+ **conf['server_info'],
165
+ key_filename=str(tempFile),
166
+ )
167
+ yield client
168
+ client.close()
169
+
170
+
171
+ async def upload():
172
+ async with sshClient() as client:
173
+
174
+ def executeCmd(cmd: str):
175
+ _, stdout, _ = client.exec_command(f"bash -lc '{cmd}'")
176
+ print(stdout.read().decode())
177
+
178
+ sftp = client.open_sftp()
179
+ for file in bpath.listFile(distPath):
180
+ executeCmd(f'mkdir -p {conf['temp_path']}')
181
+ sftp.put(str(file), f'{conf['temp_path']}/{file.name}')
182
+ shFile = list(distPath.glob('*.sh'))[0]
183
+ executeCmd(f'sh {conf['temp_path']}/{shFile.name}')
184
+ executeCmd(f'rm -rf {conf['temp_path']}')
185
+ sftp.close()
bcmd/tasks/image.py CHANGED
@@ -1,9 +1,6 @@
1
- from __future__ import annotations
2
-
3
1
  import asyncio
4
2
  import os
5
3
  import random
6
- import tkinter as tk
7
4
  from enum import StrEnum
8
5
  from pathlib import Path
9
6
  from typing import Final, List, Tuple
@@ -16,9 +13,6 @@ from beni.bfunc import syncCall
16
13
  from beni.btype import Null, XPath
17
14
  from PIL import Image, ImageDraw, ImageFont
18
15
 
19
- from bcmd.utils.tkUtil import TkForm, setWidgetEnabled
20
- from tkinter import messagebox
21
-
22
16
  app: Final = btask.newSubApp('图片工具集')
23
17
 
24
18
 
@@ -310,67 +304,66 @@ async def merge(
310
304
  bcolor.printGreen('OK')
311
305
 
312
306
 
313
- @app.command()
314
- @syncCall
315
- async def xone():
316
- def showGui():
317
- app = TkForm()
318
- app.title('图片操作')
319
- entry = app.addEntry('保存文件', tk.StringVar(value=r'C:\project\docs\source\docs\public\icon.webp'), width=60)
320
- app.addEntry('输入密码', tk.StringVar(), password=True, width=60, command=lambda: messagebox.showerror('错误', '密码错误'))
321
-
322
- scrolltextVar = tk.StringVar(value='xxiioo')
323
- app.addScrolledText('测试信箱', scrolltextVar)
324
-
325
-
326
- radioVarScale = app.addRadioBtnList(
327
- '缩放操作',
328
- [
329
- '保持',
330
- '缩放比',
331
- '指定宽度',
332
- '指定高度',
333
- ],
334
- selectedIndex=0,
335
- onChanged=lambda x: onScaleValueChanged(x),
336
- )
337
-
338
- scaleValueEntry = app.addEntry('缩放参数', tk.StringVar(), width=10, justify=tk.CENTER)
339
-
340
- app.addChoisePath('选择文件', tk.StringVar(), isDir=True, focus=True)
341
-
342
- radioVarFormat = app.addRadioBtnList(
343
- '格式转换',
344
- [
345
- '保持',
346
- 'PNG',
347
- 'JPEG',
348
- 'WEBP',
349
- ],
350
- selectedIndex=0,
351
- )
352
- app.addCheckBox('去除透明', '去除透明', False)
353
- app.addCheckBox('TinyPng', 'TinyPng', True)
354
- app.addCheckBoxList('其他选项', [
355
- ('去除透明', False),
356
- ('TinyPng', True),
357
- ])
358
-
359
- def onScaleValueChanged(value: str):
360
- match value:
361
- case '保持':
362
- setWidgetEnabled(scaleValueEntry, False)
363
- case _:
364
- setWidgetEnabled(scaleValueEntry, True)
365
-
366
- app.addBtn('确定', lambda: onBtn())
367
-
368
- def onBtn():
369
- nonlocal result
370
- app.destroy()
371
-
372
- result: str = ''
373
- app.run()
374
- return result
375
-
376
- showGui()
307
+ # @app.command()
308
+ # @syncCall
309
+ # async def xone():
310
+ # def showGui():
311
+ # app = TkForm()
312
+ # app.title('图片操作')
313
+ # entry = app.addEntry('保存文件', tk.StringVar(value=r'C:\project\docs\source\docs\public\icon.webp'), width=60)
314
+ # app.addEntry('输入密码', tk.StringVar(), password=True, width=60, command=lambda: messagebox.showerror('错误', '密码错误'))
315
+
316
+ # scrolltextVar = tk.StringVar(value='xxiioo')
317
+ # app.addScrolledText('测试信箱', scrolltextVar)
318
+
319
+ # radioVarScale = app.addRadioBtnList(
320
+ # '缩放操作',
321
+ # [
322
+ # '保持',
323
+ # '缩放比',
324
+ # '指定宽度',
325
+ # '指定高度',
326
+ # ],
327
+ # onChanged=lambda x: onScaleValueChanged(x),
328
+ # var=tk.StringVar(value='保持'),
329
+ # )
330
+
331
+ # scaleValueEntry = app.addEntry('缩放参数', tk.StringVar(), width=10, justify=tk.CENTER)
332
+
333
+ # app.addChoisePath('选择文件', tk.StringVar(), isDir=True)
334
+
335
+ # radioVarFormat = app.addRadioBtnList(
336
+ # '格式转换',
337
+ # [
338
+ # '保持',
339
+ # 'PNG',
340
+ # 'JPEG',
341
+ # 'WEBP',
342
+ # ],
343
+ # var=tk.StringVar(value='保持'),
344
+ # )
345
+ # app.addCheckBox('去除透明', '去除透明', tk.BooleanVar(value=True))
346
+ # app.addCheckBox('TinyPng', 'TinyPng', tk.BooleanVar())
347
+ # app.addCheckBoxList('其他选项', [
348
+ # ('去除透明', tk.BooleanVar()),
349
+ # ('TinyPng', tk.BooleanVar(value=True)),
350
+ # ])
351
+
352
+ # def onScaleValueChanged(value: str):
353
+ # match value:
354
+ # case '保持':
355
+ # setWidgetEnabled(scaleValueEntry, False)
356
+ # case _:
357
+ # setWidgetEnabled(scaleValueEntry, True)
358
+
359
+ # app.addBtn('确定', lambda: onBtn())
360
+
361
+ # def onBtn():
362
+ # nonlocal result
363
+ # app.destroy()
364
+
365
+ # result: str = ''
366
+ # app.run()
367
+ # return result
368
+
369
+ # showGui()
bcmd/tasks/lib.py CHANGED
@@ -1,13 +1,13 @@
1
1
  import os
2
+ import tkinter as tk
2
3
  from pathlib import Path
3
4
  from typing import Final
4
5
 
5
6
  import typer
6
7
  from beni import bcolor, bfile, bpath, brun, btask
8
+ from beni.bform import BForm
7
9
  from beni.bfunc import syncCall
8
10
 
9
- from bcmd.utils.tkUtil import TkForm
10
-
11
11
  from ..common import secret
12
12
 
13
13
  app: Final = btask.newSubApp('lib 工具')
@@ -23,7 +23,7 @@ async def update_version(
23
23
  file = path / 'pyproject.toml'
24
24
  btask.assertTrue(file.is_file(), '文件不存在', file)
25
25
  data = await bfile.readToml(file)
26
- latestVersion = data['project']['version']
26
+ latestVersion: str = data['project']['version']
27
27
  vAry = [int(x) for x in latestVersion.split('.')]
28
28
  versionList = [
29
29
  f'{vAry[0] + 1}.0.0',
@@ -31,27 +31,24 @@ async def update_version(
31
31
  f'{vAry[0]}.{vAry[1]}.{vAry[2] + 1}',
32
32
  ]
33
33
 
34
- def showGui():
35
- app = TkForm()
36
- app.title('bcmd 版本更新')
37
- app.addLabel('当前版本号', latestVersion)
38
- version_var = app.addRadioBtnList(
39
- '请选择新版本',
40
- versionList,
41
- selectedIndex=-1,
42
- )
43
- result: str = ''
34
+ class UpdateVersionForm(BForm):
44
35
 
45
- def onBtn():
46
- nonlocal result
47
- result = version_var.get()
48
- app.destroy()
36
+ def __init__(self):
37
+ super().__init__()
38
+ self.versionVar = tk.StringVar(value=versionList[-1])
39
+ self.title('bcmd 版本更新')
40
+ self.addLabel('当前版本号', latestVersion)
41
+ self.addRadioBtnList(
42
+ '请选择新版本',
43
+ versionList,
44
+ var=self.versionVar,
45
+ )
46
+ self.addBtn('确定', self.destroy, focus=True)
49
47
 
50
- app.addBtn('确定', onBtn, focus=True)
51
- app.run()
52
- return result
48
+ def getResult(self) -> str:
49
+ return self.versionVar.get()
53
50
 
54
- newVersion = showGui()
51
+ newVersion: str = UpdateVersionForm().run()
55
52
  if not newVersion:
56
53
  btask.abort('用户取消操作')
57
54
  content = await bfile.readText(file)
bcmd/tasks/upgrade.py CHANGED
@@ -11,11 +11,10 @@ app: Final = btask.app
11
11
  @app.command()
12
12
  @syncCall
13
13
  async def upgrade(
14
- name: str = typer.Argument('bcmdx', help='要更新的包名'),
14
+ name: str = typer.Argument('bcmd', help='要更新的包名'),
15
15
  ):
16
- '使用 pipx 官方源更新指定包到最新版本'
17
-
18
- cmd = f'pipx upgrade {name} -i https://pypi.org/simple'
16
+ '使用 uv 官方源更新指定包到最新版本'
17
+ cmd = f'uv tool install -U --index https://pypi.org/simple {name}'
19
18
  pyperclip.copy(cmd + '\n')
20
19
  bcolor.printGreen(cmd)
21
20
  bcolor.printGreen('已复制到剪贴板(需要手动执行)')
bcmd/utils/utils.py ADDED
@@ -0,0 +1,9 @@
1
+ from beni import bcrypto
2
+ from beni.btype import XPath
3
+
4
+
5
+ async def decryptFileIgnoreError(file: XPath, password: str):
6
+ try:
7
+ await bcrypto.decryptFile(file, password)
8
+ except:
9
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bcmd
3
- Version: 0.6.8
3
+ Version: 0.6.10
4
4
  Summary: Commands for Beni
5
5
  Author-email: Beni Mang <benimang@126.com>
6
6
  Maintainer-email: Beni Mang <benimang@126.com>
@@ -8,9 +8,10 @@ Keywords: benimang,beni,bcmd
8
8
  Requires-Python: >=3.10
9
9
  Requires-Dist: aioconsole==0.8.1
10
10
  Requires-Dist: async-lru==2.0.5
11
- Requires-Dist: benimang==0.8.1
11
+ Requires-Dist: benimang==0.8.6
12
12
  Requires-Dist: cryptography==45.0.4
13
13
  Requires-Dist: nest-asyncio==1.6.0
14
+ Requires-Dist: paramiko==3.5.1
14
15
  Requires-Dist: pillow==11.2.1
15
16
  Requires-Dist: prettytable==3.16.0
16
17
  Requires-Dist: psutil==7.0.0
@@ -1,33 +1,34 @@
1
1
  bcmd/__init__.py,sha256=GP_60-6vImXqdMfC5vc4xlscWajx4OYmnlNXASWn19w,147
2
2
  bcmd/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- bcmd/common/func.py,sha256=MB_ChA4aRzEjhMhyFmZ-h8aOZePzk0poYcTYRJUQxmI,509
3
+ bcmd/common/func.py,sha256=MehbfuWFHTOsihhYxVFj0_U6-hjMTfLh3n-oMrpyyKo,508
4
4
  bcmd/common/secret.py,sha256=clemEVTCl3shhRBgjKZfrtTdyODz5U-wOyHfUXPegG4,1692
5
5
  bcmd/resources/project/main.py,sha256=xdskz_sf05fYA1SRMFCIxDjx8SnegxTbCmHpW86ItLs,11
6
- bcmd/tasks/__init__.py,sha256=lcVrTgdEph3tKBwwCPVyEOHp-1A3zdFA_6OWLrOo19M,314
6
+ bcmd/tasks/__init__.py,sha256=_G8hO9P0UEKJRreXv8114XeTR8uFG8gRSjvA1WMv8f8,333
7
7
  bcmd/tasks/bin.py,sha256=DufZGRX7b2zclSaZM-zUPGwOiycN9RsV8KxF8tfSyqs,3240
8
8
  bcmd/tasks/code.py,sha256=IUs_ClZuSsBk2gavlitC8mkRrQQX9rvNDgR8cFxduBA,3992
9
9
  bcmd/tasks/crypto.py,sha256=LKvgsMPLvsi1wlt66TinYiN-oV2IPAfaN9y7hWaVpHs,2951
10
10
  bcmd/tasks/debian.py,sha256=B9aMIIct3vNqMJr5hTr1GegXVf20H49C27FMvRRGIzI,3004
11
+ bcmd/tasks/docs.py,sha256=pVs9iy9fjo4DrWX-YfrxjnxLOO4VAW7w5NUqIZHxnVQ,5927
11
12
  bcmd/tasks/download.py,sha256=XdZYKi8zQTNYWEgUxeTNDqPgP7IGYJkMmlDDC9u93Vk,2315
12
- bcmd/tasks/image.py,sha256=OQxrtTkPnJ_FZ6uenxdNlynhxwrAVy1P_68QAnSBYB0,14238
13
+ bcmd/tasks/image.py,sha256=_ck-WVfUlyQ2fZTpVPcpcurWSud7AkANKUuFjMW7MwA,14283
13
14
  bcmd/tasks/json.py,sha256=WWOyvcZPYaqQgp-Tkm-uIJschNMBKPKtZN3yXz_SC5s,635
14
- bcmd/tasks/lib.py,sha256=ODF21sTjQ687X5AXGOvJjjw6dGNSnflwSz4wDlIbYVM,2861
15
+ bcmd/tasks/lib.py,sha256=CmIRUHzNSa6s0Ft2oYZcfEr-5YwvxrVCNM55qq9sBXg,2948
15
16
  bcmd/tasks/math.py,sha256=xbl5UdaDMyAjiLodDPleP4Cutrk2S3NOAgurzAgOEAE,2862
16
17
  bcmd/tasks/mirror.py,sha256=nAe8NYftMKzht16MFBj7RqXwvVhR6Jh2uuAyJLh87og,1098
17
18
  bcmd/tasks/pdf.py,sha256=fkHRgxqzrRxdb4_-9pL9wp2roqAHJPS_dVqAGJvRUsM,1504
18
19
  bcmd/tasks/proxy.py,sha256=KDOXtNRoY4n45tBrAgK-UM7GmvCCx3Ho545yxMc8PKQ,1602
19
20
  bcmd/tasks/time.py,sha256=ZiqA1jdgl-TBtFSOxxP51nwv4g9iZItmkFKpf9MKelk,2453
20
- bcmd/tasks/upgrade.py,sha256=z9Ein8U_Co7fsijPxyDFM8tXBFCp4bWATImwwwvC5ho,536
21
+ bcmd/tasks/upgrade.py,sha256=nyhcl5oGAUnOOR8JJZW2jfepcVJ6O9gufK8VgxUeil0,543
21
22
  bcmd/tasks/wasabi.py,sha256=xWFAxprSIlBqDDMGaNXZFb-SahnW1d_R9XxSKRYIhnM,3110
22
23
  bcmd/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- bcmd/utils/tkUtil.py,sha256=zhOFQB0SN-ZPSXgJ0-pyHeVldf_u8VNnODz7pQRbLkw,7709
24
+ bcmd/utils/utils.py,sha256=Oyf5ubxaCD_T2GOz3zJkR4AJzfVcfBqtmL8Gk8_gOJs,202
24
25
  test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
26
  test/conftest.py,sha256=grlPunlsvrkt_8QPckmF4POiKUPVxIxm2TPAh_ZB-zs,405
26
27
  test/test_pdf.py,sha256=7yYlfydyhy2dmVYdTA5Vir2AI8TUdzEi55fL-AqJmio,1533
27
28
  test/test_proxy.py,sha256=UMF2hFFGUEbJR1jT2mO_wdo-7Rfp0NDqIdTRnOmwtjY,164
28
29
  test/test_wasabi.py,sha256=qqXG1Kb9hKH6t624R173j6LagkgmejN0CFYt7kL0nNs,1066
29
- bcmd-0.6.8.dist-info/METADATA,sha256=cN10mQH_WWDQ2McdparDnOZkc6KS5a0JOULMxkd8sXs,812
30
- bcmd-0.6.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- bcmd-0.6.8.dist-info/entry_points.txt,sha256=mriCeYh3wksKcqq3-LtzyFkSCIdN1uZc1IJwom-SW1s,34
32
- bcmd-0.6.8.dist-info/top_level.txt,sha256=fYY6tRrJ_G7tn24RXAG0M5ZKbcuaQznodfX1toFPSKs,10
33
- bcmd-0.6.8.dist-info/RECORD,,
30
+ bcmd-0.6.10.dist-info/METADATA,sha256=x2T8YTvQ8vQxS0aiegxZNmPoMgqp76gyfeBpRYd79l4,845
31
+ bcmd-0.6.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
+ bcmd-0.6.10.dist-info/entry_points.txt,sha256=mriCeYh3wksKcqq3-LtzyFkSCIdN1uZc1IJwom-SW1s,34
33
+ bcmd-0.6.10.dist-info/top_level.txt,sha256=fYY6tRrJ_G7tn24RXAG0M5ZKbcuaQznodfX1toFPSKs,10
34
+ bcmd-0.6.10.dist-info/RECORD,,
bcmd/utils/tkUtil.py DELETED
@@ -1,243 +0,0 @@
1
- import tkinter as tk
2
- from tkinter import filedialog
3
- from tkinter.scrolledtext import ScrolledText
4
- from typing import Any, Callable, Literal, TypeVar, Union
5
- from uuid import uuid4
6
-
7
- RADIO_NOTHING = uuid4().hex
8
- TkVar = TypeVar('TkVar', bound=Union[tk.StringVar, tk.IntVar, tk.DoubleVar, tk.BooleanVar])
9
-
10
-
11
- class TkForm(tk.Tk):
12
-
13
- _rowIndex = -1
14
- _initList: list[Callable[..., None]] = []
15
- _varList: list[tk.Variable] = [] # 用来存储 var 变量,避免传出去,外面没有接收导致界面异常
16
-
17
- def __init__(self):
18
- super().__init__()
19
- self.resizable(False, False)
20
- self.bind("<Map>", self._onInit)
21
-
22
- def _onInit(self, evt: tk.Event):
23
- if evt.widget == self:
24
- for callback in self._initList:
25
- callback()
26
- self._initList.clear()
27
-
28
- def addInitHandler(self, handler: Callable[..., None]):
29
- self._initList.append(handler)
30
-
31
- def _initVar(self, var: TkVar) -> TkVar:
32
- self._varList.append(var)
33
- return var
34
-
35
- def run(self):
36
- self.center()
37
- self.mainloop()
38
-
39
- def center(self):
40
- self.withdraw() # 先隐藏窗口,避免闪动
41
- self.update_idletasks() # 确保获取正确的窗口尺寸
42
- width = self.winfo_width() # 获取窗口宽度
43
- height = self.winfo_height() # 获取窗口高度
44
- screen_width = self.winfo_screenwidth() # 屏幕宽度
45
- screen_height = self.winfo_screenheight() # 屏幕高度
46
- x = (screen_width - width) // 2 # 水平居中
47
- y = (screen_height - height) // 2 # 垂直居中
48
- self.geometry(f"+{x}+{y}") # 设置窗口位置
49
- self.deiconify() # 恢复显示窗口
50
-
51
- def addRow(self, desc: str, widget: tk.Widget):
52
- self._rowIndex += 1
53
- tk.Label(text=desc).grid(row=self._rowIndex, column=0, padx=10, pady=5, sticky='e')
54
- widget.grid(row=self._rowIndex, column=1, padx=10, pady=5, sticky='w')
55
-
56
- def addRowFrame(self):
57
- self._rowIndex += 1
58
- frame = tk.Frame(self)
59
- frame.grid(row=self._rowIndex, column=0, columnspan=2, padx=10, pady=5)
60
- return frame
61
-
62
- def addRowFrameWithDesc(self, desc: str):
63
- self._rowIndex += 1
64
- tk.Label(text=desc).grid(row=self._rowIndex, column=0, padx=10, pady=5, sticky='e')
65
- frame = tk.Frame(self)
66
- frame.grid(row=self._rowIndex, column=1, padx=10, pady=5, sticky='w')
67
- return frame
68
-
69
- def addLabel(
70
- self,
71
- desc: str,
72
- text: str
73
- ):
74
- self.addRow(desc, tk.Label(text=text))
75
-
76
- def addBtn(
77
- self,
78
- label: str,
79
- command: Callable[..., None],
80
- *,
81
- width: int = 20,
82
- focus: bool = False
83
- ):
84
- frame = self.addRowFrame()
85
- btn = tk.Button(frame, text=label, width=width, command=command)
86
- btn.pack(side="left", expand=True, padx=15)
87
- if focus:
88
- self._initFocus(btn)
89
-
90
- def addRadioBtnList(
91
- self,
92
- desc: str,
93
- selectionList: list[str],
94
- *,
95
- selectedIndex: int | None = None,
96
- focusIndex: int | None = None,
97
- onChanged: Callable[[str], None] | None = None,
98
- ):
99
- frame = tk.Frame()
100
- self.addRow(desc, frame)
101
- var = tk.StringVar(value=selectionList[selectedIndex] if selectedIndex is not None else RADIO_NOTHING)
102
- radioBtnList: list[tk.Radiobutton] = []
103
- for version in selectionList:
104
- radioBtn = tk.Radiobutton(frame, text=version, variable=var, value=version)
105
- radioBtn.pack(side="left", padx=(0, 15))
106
- setWidgetClickFocus(radioBtn)
107
- radioBtnList.append(radioBtn)
108
- if focusIndex is not None:
109
- self._initFocus(radioBtnList[focusIndex])
110
- if onChanged:
111
- var.trace_add('write', lambda *args: onChanged(var.get())) # type: ignore
112
- self.addInitHandler(
113
- lambda: onChanged(var.get())
114
- )
115
- return var
116
-
117
- def addEntry(
118
- self,
119
- desc: str,
120
- var: tk.StringVar,
121
- *,
122
- width: int = 60,
123
- focus: bool = False,
124
- justify: Literal['left', 'right', 'center'] = tk.LEFT,
125
- password: bool = False,
126
- command: Callable[..., Any] | None = None,
127
- ):
128
- self._initVar(var)
129
- entry = tk.Entry(self, width=width, justify=justify, textvariable=var)
130
- entry.icursor(tk.END)
131
- self.addRow(desc, entry)
132
- if password:
133
- entry.config(show='*')
134
- if focus:
135
- self._initFocus(entry)
136
- if command:
137
- entry.bind('<Return>', lambda event: command())
138
- return entry
139
-
140
- def addChoisePath(
141
- self,
142
- desc: str,
143
- var: tk.StringVar,
144
- *,
145
- width: int = 47,
146
- focus: bool = False,
147
- isDir: bool = False,
148
- ):
149
- self._initVar(var)
150
- frame = self.addRowFrameWithDesc(desc)
151
- entry = tk.Entry(frame, width=width, textvariable=var)
152
- entry.icursor(tk.END)
153
- entry.pack(side="left")
154
- btn = tk.Button(frame, text=f'选择{'目录' if isDir else '文件'} ...', width=10, command=lambda: onBtn())
155
- btn.pack(side="left", padx=(10, 0))
156
- if focus:
157
- self._initFocus(btn)
158
-
159
- def onBtn():
160
- if isDir:
161
- var.set(filedialog.askdirectory())
162
- else:
163
- var.set(filedialog.askopenfilename())
164
-
165
- def addScrolledText(
166
- self,
167
- desc: str,
168
- var: tk.StringVar,
169
- *,
170
- width: int = 60,
171
- height: int = 3,
172
- focus: bool = False,
173
- ):
174
- self._initVar(var)
175
- scrolledText = ScrolledText(self, width=width, height=height)
176
- scrolledText.insert(tk.END, var.get())
177
- self.addRow(desc, scrolledText)
178
-
179
- def on_text_change(*args: Any):
180
- new_value = scrolledText.get("1.0", tk.END)
181
- if new_value != var.get():
182
- var.set(new_value)
183
-
184
- scrolledText.bind("<KeyRelease>", on_text_change)
185
-
186
- def on_tab(event: tk.Event):
187
- widget = event.widget.tk_focusNext()
188
- assert widget
189
- widget.focus_set()
190
- return "break"
191
-
192
- scrolledText.bind("<Tab>", on_tab)
193
-
194
- if focus:
195
- self._initFocus(scrolledText)
196
- return scrolledText
197
-
198
- def addCheckBox(
199
- self,
200
- desc: str,
201
- text: str,
202
- value: bool = False,
203
- ):
204
- var = tk.BooleanVar(value=value)
205
- self._initVar(var)
206
- check_btn = tk.Checkbutton(text=text, variable=var)
207
- self.addRow(desc, check_btn)
208
- self.addInitHandler(
209
- lambda: var.set(var.get())
210
- )
211
- setWidgetClickFocus(check_btn)
212
- return var
213
-
214
- def addCheckBoxList(
215
- self,
216
- desc: str,
217
- dataList: list[tuple[str, bool]],
218
- ):
219
- varDict: dict[str, tk.BooleanVar] = {}
220
- frame = tk.Frame(self)
221
- self.addRow(desc, frame)
222
- for label, value in dataList:
223
- varDict[label] = tk.BooleanVar(value=value)
224
- checkbox = tk.Checkbutton(frame, text=label, variable=varDict[label])
225
- checkbox.pack(side="left", expand=True, padx=(0, 15))
226
- setWidgetClickFocus(checkbox)
227
- self.addInitHandler(
228
- lambda: varDict[label].set(varDict[label].get())
229
- )
230
- return varDict
231
-
232
- def _initFocus(self, widget: tk.Widget):
233
- self.addInitHandler(
234
- lambda: widget.focus_set()
235
- )
236
-
237
-
238
- def setWidgetEnabled(widget: tk.Widget, value: bool):
239
- widget['state'] = tk.NORMAL if value else tk.DISABLED
240
-
241
-
242
- def setWidgetClickFocus(widget: tk.Widget):
243
- widget.bind("<Button-1>", lambda args: args.widget.focus_set())
File without changes