bcmd 0.6.5__py3-none-any.whl → 0.6.7__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/secret.py +51 -0
- {bcmdx → bcmd}/tasks/bin.py +13 -14
- {bcmdx → bcmd}/tasks/image.py +72 -0
- {bcmdx → bcmd}/tasks/lib.py +6 -5
- bcmd/utils/tkUtil.py +243 -0
- {bcmd-0.6.5.dist-info → bcmd-0.6.7.dist-info}/METADATA +1 -1
- bcmd-0.6.7.dist-info/RECORD +33 -0
- bcmd-0.6.7.dist-info/entry_points.txt +2 -0
- bcmd-0.6.7.dist-info/top_level.txt +2 -0
- test/conftest.py +17 -0
- test/test_pdf.py +47 -0
- test/test_proxy.py +8 -0
- test/test_wasabi.py +28 -0
- bcmd-0.6.5.dist-info/RECORD +0 -29
- bcmd-0.6.5.dist-info/entry_points.txt +0 -2
- bcmd-0.6.5.dist-info/top_level.txt +0 -1
- bcmdx/common/password.py +0 -35
- bcmdx/utils/tkUtil.py +0 -130
- /bcmdx/main.py → /bcmd/__init__.py +0 -0
- {bcmdx → bcmd/common}/__init__.py +0 -0
- {bcmdx → bcmd}/common/func.py +0 -0
- {bcmdx → bcmd}/resources/project/main.py +0 -0
- {bcmdx → bcmd}/tasks/__init__.py +0 -0
- {bcmdx → bcmd}/tasks/code.py +0 -0
- {bcmdx → bcmd}/tasks/crypto.py +0 -0
- {bcmdx → bcmd}/tasks/debian.py +0 -0
- {bcmdx → bcmd}/tasks/download.py +0 -0
- {bcmdx → bcmd}/tasks/json.py +0 -0
- {bcmdx → bcmd}/tasks/math.py +0 -0
- {bcmdx → bcmd}/tasks/mirror.py +0 -0
- {bcmdx → bcmd}/tasks/pdf.py +0 -0
- {bcmdx → bcmd}/tasks/proxy.py +0 -0
- {bcmdx → bcmd}/tasks/time.py +0 -0
- {bcmdx → bcmd}/tasks/upgrade.py +0 -0
- {bcmdx → bcmd}/tasks/wasabi.py +0 -0
- {bcmdx/common → bcmd/utils}/__init__.py +0 -0
- {bcmd-0.6.5.dist-info → bcmd-0.6.7.dist-info}/WHEEL +0 -0
- {bcmdx/utils → test}/__init__.py +0 -0
bcmd/common/secret.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import getpass
|
|
2
|
+
from typing import Any, TypedDict, cast
|
|
3
|
+
|
|
4
|
+
from async_lru import alru_cache
|
|
5
|
+
from beni import bcrypto, btask
|
|
6
|
+
from beni.bcolor import printRed
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PypiSecret(TypedDict):
|
|
10
|
+
username: str
|
|
11
|
+
password: str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@alru_cache
|
|
15
|
+
async def getPypi(value: str = '') -> PypiSecret:
|
|
16
|
+
return cast(
|
|
17
|
+
Any,
|
|
18
|
+
_getData(value or 'QbuF2mV/lqovtF5dskZGD7qHknYbNuF2QseWRtWxLZTPrC/jL1tcxV8JEKaRjLsu46PxJZ7zepJwggnUTIWnEAoV5VtgP2/hbuzxxHha8817kR5c65H9fXm8eOal7DYXsUoGPQMnm59UWNXUKjmIaP4sn9nySFlRYqa8sEZSbYQ4N0NL35Dpj1e3wyQxJ+7h2jwKAz50Hh8G4yAM3/js9+NUe4ymts+UXcwsP3ADIBMkzjnFc0lEYg2d+fw0A74XWCvoZPoGqHZR/THUOVNAYxoGgDzP4SPIk1XsmtpxvfO/DpJd/Cg/0fB3MYagGKI1+m6Bxqhvd1I/lf0YbM5y4E4='),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class QiniuSecret(TypedDict):
|
|
23
|
+
bucket: str
|
|
24
|
+
baseUrl: str
|
|
25
|
+
ak: str
|
|
26
|
+
sk: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@alru_cache
|
|
30
|
+
async def getQiniu(value: str = '') -> QiniuSecret:
|
|
31
|
+
return cast(
|
|
32
|
+
Any,
|
|
33
|
+
_getData(value or 'vNroFKeKklrdcJ89suFm+iyuJsq/cyUB5+QWoeeiMc/J0oSLF9cg5rqbK1IRxF0cCQ8KmkQQhdVa+PI6kuTBhoSH6IviVTylzAOrJywEccz9jWkJkW28Y9Vo4ePZmfWf/j7wdxNB144z234KD8IxJn4lR2A0L9JN5kk1o1/hpcydXL74FNtt03lYL/E3WVcvpUfw37mri2HMYOfUw81dRwW35/hMuQjtq1BBrKrIsSKTHH44tROMcgyvt+Qy292AtDBcsYiZxBKhQtBFPMq/vUs='),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _getData(content: str) -> dict[str, Any]:
|
|
38
|
+
index = content.find(' ')
|
|
39
|
+
if index > -1:
|
|
40
|
+
tips = f'请输入密码({content[:index]}):'
|
|
41
|
+
else:
|
|
42
|
+
tips = '请输入密码:'
|
|
43
|
+
while True:
|
|
44
|
+
try:
|
|
45
|
+
pwd = getpass.getpass(tips)
|
|
46
|
+
return bcrypto.decryptJson(content, pwd)
|
|
47
|
+
except KeyboardInterrupt:
|
|
48
|
+
print('')
|
|
49
|
+
btask.abort('用户操作取消')
|
|
50
|
+
except BaseException:
|
|
51
|
+
printRed('密码错误')
|
{bcmdx → bcmd}/tasks/bin.py
RENAMED
|
@@ -9,7 +9,7 @@ from beni.bqiniu import QiniuBucket
|
|
|
9
9
|
from beni.btype import Null
|
|
10
10
|
from prettytable import PrettyTable
|
|
11
11
|
|
|
12
|
-
from ..common import
|
|
12
|
+
from ..common import secret
|
|
13
13
|
|
|
14
14
|
app: Final = btask.newSubApp('bin 工具')
|
|
15
15
|
|
|
@@ -22,6 +22,7 @@ async def download(
|
|
|
22
22
|
names: list[str] = typer.Argument(None, help="支持多个"),
|
|
23
23
|
file: Path = typer.Option(None, '--file', '-f', help="文件形式指定参数,行为单位"),
|
|
24
24
|
output: Path = typer.Option(Path.cwd(), '--output', '-o', help="本地保存路径"),
|
|
25
|
+
secretValue: str = typer.Option('', '--secret', '-s', help='密钥信息'),
|
|
25
26
|
):
|
|
26
27
|
'从七牛云下载执行文件'
|
|
27
28
|
bucket: QiniuBucket = Null
|
|
@@ -36,16 +37,18 @@ async def download(
|
|
|
36
37
|
bcolor.printYellow(f'已存在 {binFile}')
|
|
37
38
|
else:
|
|
38
39
|
key = f'bin/{target}.zip'
|
|
39
|
-
bucket = bucket or await _getBucket()
|
|
40
|
+
bucket = bucket or await _getBucket(secretValue)
|
|
40
41
|
await bucket.downloadPrivateFileUnzip(key, output)
|
|
41
42
|
bcolor.printGreen(f'added {binFile}')
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
@app.command('list')
|
|
45
46
|
@syncCall
|
|
46
|
-
async def getList(
|
|
47
|
+
async def getList(
|
|
48
|
+
secretValue: str = typer.Option('', '--secret', '-s', help='密钥信息'),
|
|
49
|
+
):
|
|
47
50
|
'列出可下载的文件'
|
|
48
|
-
bucket = await _getBucket()
|
|
51
|
+
bucket = await _getBucket(secretValue)
|
|
49
52
|
datas = (await bucket.getFileList(_PREFIX, limit=1000))[0]
|
|
50
53
|
datas = [x for x in datas if x.key != _PREFIX and x.key.endswith('.zip')]
|
|
51
54
|
datas.sort(key=lambda x: x.time, reverse=True)
|
|
@@ -68,9 +71,10 @@ async def getList():
|
|
|
68
71
|
async def upload(
|
|
69
72
|
file: Path = typer.Argument(..., help="本地文件路径"),
|
|
70
73
|
force: bool = typer.Option(False, '--force', '-f', help="强制覆盖"),
|
|
74
|
+
secretValue: str = typer.Option('', '--secret', '-s', help='密钥信息'),
|
|
71
75
|
):
|
|
72
76
|
'上传'
|
|
73
|
-
bucket = await _getBucket()
|
|
77
|
+
bucket = await _getBucket(secretValue)
|
|
74
78
|
key = f'{_PREFIX}{file.name}.zip'
|
|
75
79
|
if not force:
|
|
76
80
|
if await bucket.getFileStatus(key):
|
|
@@ -85,8 +89,9 @@ async def upload(
|
|
|
85
89
|
@syncCall
|
|
86
90
|
async def remove(
|
|
87
91
|
key: str = typer.Argument(..., help="云端文件key"),
|
|
92
|
+
secretValue: str = typer.Option('', '--secret', '-s', help='密钥信息'),
|
|
88
93
|
):
|
|
89
|
-
bucket = await _getBucket()
|
|
94
|
+
bucket = await _getBucket(secretValue)
|
|
90
95
|
await bucket.deleteFiles(f'{_PREFIX}{key}.zip')
|
|
91
96
|
bcolor.printGreen('OK')
|
|
92
97
|
|
|
@@ -94,11 +99,5 @@ async def remove(
|
|
|
94
99
|
# ------------------------------------------------------------------------------------
|
|
95
100
|
|
|
96
101
|
|
|
97
|
-
async def _getBucket():
|
|
98
|
-
|
|
99
|
-
return QiniuBucket(
|
|
100
|
-
'pytask',
|
|
101
|
-
'http://qiniu-cdn.pytask.com',
|
|
102
|
-
ak,
|
|
103
|
-
sk,
|
|
104
|
-
)
|
|
102
|
+
async def _getBucket(secretValue: str):
|
|
103
|
+
return QiniuBucket(**await secret.getQiniu())
|
{bcmdx → bcmd}/tasks/image.py
RENAMED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import os
|
|
3
5
|
import random
|
|
6
|
+
import tkinter as tk
|
|
4
7
|
from enum import StrEnum
|
|
5
8
|
from pathlib import Path
|
|
6
9
|
from typing import Final, List, Tuple
|
|
@@ -13,6 +16,9 @@ from beni.bfunc import syncCall
|
|
|
13
16
|
from beni.btype import Null, XPath
|
|
14
17
|
from PIL import Image, ImageDraw, ImageFont
|
|
15
18
|
|
|
19
|
+
from bcmd.utils.tkUtil import TkForm, setWidgetEnabled
|
|
20
|
+
from tkinter import messagebox
|
|
21
|
+
|
|
16
22
|
app: Final = btask.newSubApp('图片工具集')
|
|
17
23
|
|
|
18
24
|
|
|
@@ -302,3 +308,69 @@ async def merge(
|
|
|
302
308
|
_merge_images(image_files, output_image)
|
|
303
309
|
bcolor.printMagenta(output_image)
|
|
304
310
|
bcolor.printGreen('OK')
|
|
311
|
+
|
|
312
|
+
|
|
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()
|
{bcmdx → bcmd}/tasks/lib.py
RENAMED
|
@@ -6,9 +6,9 @@ import typer
|
|
|
6
6
|
from beni import bcolor, bfile, bpath, brun, btask
|
|
7
7
|
from beni.bfunc import syncCall
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from bcmd.utils.tkUtil import TkForm
|
|
10
10
|
|
|
11
|
-
from ..common import
|
|
11
|
+
from ..common import secret
|
|
12
12
|
|
|
13
13
|
app: Final = btask.newSubApp('lib 工具')
|
|
14
14
|
|
|
@@ -35,7 +35,7 @@ async def update_version(
|
|
|
35
35
|
app = TkForm()
|
|
36
36
|
app.title('bcmd 版本更新')
|
|
37
37
|
app.addLabel('当前版本号', latestVersion)
|
|
38
|
-
version_var = app.
|
|
38
|
+
version_var = app.addRadioBtnList(
|
|
39
39
|
'请选择新版本',
|
|
40
40
|
versionList,
|
|
41
41
|
selectedIndex=-1,
|
|
@@ -80,14 +80,15 @@ async def update_version(
|
|
|
80
80
|
@syncCall
|
|
81
81
|
async def build(
|
|
82
82
|
path: Path = typer.Argument(Path.cwd(), help='workspace 路径'),
|
|
83
|
+
secretValue: str = typer.Option('', '--secret', '-s', help='密钥信息'),
|
|
83
84
|
):
|
|
84
85
|
'发布项目'
|
|
85
|
-
|
|
86
|
+
data = await secret.getPypi(secretValue)
|
|
86
87
|
bpath.remove(path / 'dist')
|
|
87
88
|
bpath.remove(
|
|
88
89
|
*list(path.glob('*.egg-info'))
|
|
89
90
|
)
|
|
90
91
|
with bpath.changePath(path):
|
|
91
92
|
await brun.run(f'uv build')
|
|
92
|
-
await brun.run(f'uv publish -u {
|
|
93
|
+
await brun.run(f'uv publish -u {data['username']} -p {data['password']}')
|
|
93
94
|
bcolor.printGreen('OK')
|
bcmd/utils/tkUtil.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
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())
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
bcmd/__init__.py,sha256=GP_60-6vImXqdMfC5vc4xlscWajx4OYmnlNXASWn19w,147
|
|
2
|
+
bcmd/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
bcmd/common/func.py,sha256=MB_ChA4aRzEjhMhyFmZ-h8aOZePzk0poYcTYRJUQxmI,509
|
|
4
|
+
bcmd/common/secret.py,sha256=clemEVTCl3shhRBgjKZfrtTdyODz5U-wOyHfUXPegG4,1692
|
|
5
|
+
bcmd/resources/project/main.py,sha256=xdskz_sf05fYA1SRMFCIxDjx8SnegxTbCmHpW86ItLs,11
|
|
6
|
+
bcmd/tasks/__init__.py,sha256=lcVrTgdEph3tKBwwCPVyEOHp-1A3zdFA_6OWLrOo19M,314
|
|
7
|
+
bcmd/tasks/bin.py,sha256=DufZGRX7b2zclSaZM-zUPGwOiycN9RsV8KxF8tfSyqs,3240
|
|
8
|
+
bcmd/tasks/code.py,sha256=IUs_ClZuSsBk2gavlitC8mkRrQQX9rvNDgR8cFxduBA,3992
|
|
9
|
+
bcmd/tasks/crypto.py,sha256=LKvgsMPLvsi1wlt66TinYiN-oV2IPAfaN9y7hWaVpHs,2951
|
|
10
|
+
bcmd/tasks/debian.py,sha256=B9aMIIct3vNqMJr5hTr1GegXVf20H49C27FMvRRGIzI,3004
|
|
11
|
+
bcmd/tasks/download.py,sha256=XdZYKi8zQTNYWEgUxeTNDqPgP7IGYJkMmlDDC9u93Vk,2315
|
|
12
|
+
bcmd/tasks/image.py,sha256=OQxrtTkPnJ_FZ6uenxdNlynhxwrAVy1P_68QAnSBYB0,14238
|
|
13
|
+
bcmd/tasks/json.py,sha256=WWOyvcZPYaqQgp-Tkm-uIJschNMBKPKtZN3yXz_SC5s,635
|
|
14
|
+
bcmd/tasks/lib.py,sha256=ODF21sTjQ687X5AXGOvJjjw6dGNSnflwSz4wDlIbYVM,2861
|
|
15
|
+
bcmd/tasks/math.py,sha256=xbl5UdaDMyAjiLodDPleP4Cutrk2S3NOAgurzAgOEAE,2862
|
|
16
|
+
bcmd/tasks/mirror.py,sha256=nAe8NYftMKzht16MFBj7RqXwvVhR6Jh2uuAyJLh87og,1098
|
|
17
|
+
bcmd/tasks/pdf.py,sha256=fkHRgxqzrRxdb4_-9pL9wp2roqAHJPS_dVqAGJvRUsM,1504
|
|
18
|
+
bcmd/tasks/proxy.py,sha256=xvxN5PClUnc5LQpmq2Wug7_LUVpJboMWLXBvL9lX7EM,1552
|
|
19
|
+
bcmd/tasks/time.py,sha256=ZiqA1jdgl-TBtFSOxxP51nwv4g9iZItmkFKpf9MKelk,2453
|
|
20
|
+
bcmd/tasks/upgrade.py,sha256=z9Ein8U_Co7fsijPxyDFM8tXBFCp4bWATImwwwvC5ho,536
|
|
21
|
+
bcmd/tasks/wasabi.py,sha256=xWFAxprSIlBqDDMGaNXZFb-SahnW1d_R9XxSKRYIhnM,3110
|
|
22
|
+
bcmd/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
bcmd/utils/tkUtil.py,sha256=zhOFQB0SN-ZPSXgJ0-pyHeVldf_u8VNnODz7pQRbLkw,7709
|
|
24
|
+
test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
test/conftest.py,sha256=grlPunlsvrkt_8QPckmF4POiKUPVxIxm2TPAh_ZB-zs,405
|
|
26
|
+
test/test_pdf.py,sha256=7yYlfydyhy2dmVYdTA5Vir2AI8TUdzEi55fL-AqJmio,1533
|
|
27
|
+
test/test_proxy.py,sha256=UMF2hFFGUEbJR1jT2mO_wdo-7Rfp0NDqIdTRnOmwtjY,164
|
|
28
|
+
test/test_wasabi.py,sha256=qqXG1Kb9hKH6t624R173j6LagkgmejN0CFYt7kL0nNs,1066
|
|
29
|
+
bcmd-0.6.7.dist-info/METADATA,sha256=XVbdxqxA2VLOsMucxKpgGgVgslDi1nBM4QtLoCSDYvU,812
|
|
30
|
+
bcmd-0.6.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
31
|
+
bcmd-0.6.7.dist-info/entry_points.txt,sha256=mriCeYh3wksKcqq3-LtzyFkSCIdN1uZc1IJwom-SW1s,34
|
|
32
|
+
bcmd-0.6.7.dist-info/top_level.txt,sha256=fYY6tRrJ_G7tn24RXAG0M5ZKbcuaQznodfX1toFPSKs,10
|
|
33
|
+
bcmd-0.6.7.dist-info/RECORD,,
|
test/conftest.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# '''
|
|
2
|
+
# 快捷键(默认)
|
|
3
|
+
# CTRL+; A 执行全部单元测试
|
|
4
|
+
# CTRL+; E 只执行上次出错的用例
|
|
5
|
+
# CTRL+; C 清除结果
|
|
6
|
+
# CTRL+; CTRL+A 调试全部单元测试
|
|
7
|
+
# CTRL+; CTRL+E 只调试上次出错的用例
|
|
8
|
+
# '''
|
|
9
|
+
|
|
10
|
+
import pytest_asyncio
|
|
11
|
+
|
|
12
|
+
from bcmd.tasks import *
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest_asyncio.fixture(scope='session', autouse=True) # type: ignore
|
|
16
|
+
def prepareSession():
|
|
17
|
+
yield
|
test/test_pdf.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import img2pdf
|
|
6
|
+
import pytest
|
|
7
|
+
from beni import bfile, bpath, btask
|
|
8
|
+
from PIL import Image
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.asyncio
|
|
12
|
+
async def test_pdf_output_images():
|
|
13
|
+
async with _createTempPdfFile() as pdfFile:
|
|
14
|
+
result = btask.testCall('pdf', 'output-images', '--target', pdfFile.as_posix())
|
|
15
|
+
assert result.exit_code == 0
|
|
16
|
+
outputImagesPath = pdfFile.parent / f'{pdfFile.stem}-PDF图片文件'
|
|
17
|
+
assert outputImagesPath.is_dir()
|
|
18
|
+
assert len(list(outputImagesPath.glob('*.png'))) == 1
|
|
19
|
+
assert len(list(outputImagesPath.glob('*.jpeg'))) == 1
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@asynccontextmanager
|
|
23
|
+
async def _createTempPdfFile():
|
|
24
|
+
|
|
25
|
+
def create_color_block_image(
|
|
26
|
+
file: Path,
|
|
27
|
+
color: tuple[int, int, int],
|
|
28
|
+
size: tuple[int, int] = (500, 500),
|
|
29
|
+
image_format: Literal['JPEG', 'PNG'] = 'JPEG'
|
|
30
|
+
):
|
|
31
|
+
image = Image.new('RGB', size, color)
|
|
32
|
+
image.save(file, format=image_format)
|
|
33
|
+
image.close()
|
|
34
|
+
return file
|
|
35
|
+
|
|
36
|
+
with bpath.useTempPath(True) as tempPath:
|
|
37
|
+
# 创建色块图片
|
|
38
|
+
fileList = [
|
|
39
|
+
create_color_block_image(tempPath / 'blue.png', (0, 0, 255), image_format='PNG'),
|
|
40
|
+
create_color_block_image(tempPath / 'red.jpeg', (255, 0, 0), image_format='JPEG'),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
# 生成PDF文件
|
|
44
|
+
pdfFile = tempPath / 'output.pdf'
|
|
45
|
+
await bfile.writeBytes(pdfFile, img2pdf.convert(fileList)) # type: ignore
|
|
46
|
+
|
|
47
|
+
yield pdfFile
|
test/test_proxy.py
ADDED
test/test_wasabi.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from uuid import uuid4
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from beni import bfile, bpath, btask
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.asyncio
|
|
8
|
+
async def test_wasabi():
|
|
9
|
+
with bpath.useTempPath() as tempPath:
|
|
10
|
+
target = tempPath / uuid4().hex
|
|
11
|
+
file = target / uuid4().hex
|
|
12
|
+
content = uuid4().hex
|
|
13
|
+
password = uuid4().hex
|
|
14
|
+
await bfile.writeText(file, content)
|
|
15
|
+
|
|
16
|
+
result = btask.testCall('wasabi', 'zip', target.as_posix(), '--password', password)
|
|
17
|
+
assert result.exit_code == 0
|
|
18
|
+
assert target.is_file(), '生成加密文件失败'
|
|
19
|
+
|
|
20
|
+
newPassword = uuid4().hex
|
|
21
|
+
result = btask.testCall('wasabi', 'change-pass', target.as_posix(), '--password', password, '--new-password', newPassword)
|
|
22
|
+
assert result.exit_code == 0
|
|
23
|
+
assert target.is_file(), '修改密码失败'
|
|
24
|
+
|
|
25
|
+
result = btask.testCall('wasabi', 'unzip', target.as_posix(), '--password', newPassword)
|
|
26
|
+
assert result.exit_code == 0
|
|
27
|
+
assert target.is_dir(), '解密文件失败'
|
|
28
|
+
assert await bfile.readText(file) == content, '解密文件内容不一致'
|
bcmd-0.6.5.dist-info/RECORD
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
bcmdx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
bcmdx/main.py,sha256=GP_60-6vImXqdMfC5vc4xlscWajx4OYmnlNXASWn19w,147
|
|
3
|
-
bcmdx/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
bcmdx/common/func.py,sha256=MB_ChA4aRzEjhMhyFmZ-h8aOZePzk0poYcTYRJUQxmI,509
|
|
5
|
-
bcmdx/common/password.py,sha256=2oxHvsCL05lNBpDJ8hbx4LPadDsZQKds4f0rJBYTNHA,1368
|
|
6
|
-
bcmdx/resources/project/main.py,sha256=xdskz_sf05fYA1SRMFCIxDjx8SnegxTbCmHpW86ItLs,11
|
|
7
|
-
bcmdx/tasks/__init__.py,sha256=lcVrTgdEph3tKBwwCPVyEOHp-1A3zdFA_6OWLrOo19M,314
|
|
8
|
-
bcmdx/tasks/bin.py,sha256=B_e-HYOc2Cohk-1PbKHyKn3RhI8TAUfI2EBRoFEHiTM,2961
|
|
9
|
-
bcmdx/tasks/code.py,sha256=IUs_ClZuSsBk2gavlitC8mkRrQQX9rvNDgR8cFxduBA,3992
|
|
10
|
-
bcmdx/tasks/crypto.py,sha256=LKvgsMPLvsi1wlt66TinYiN-oV2IPAfaN9y7hWaVpHs,2951
|
|
11
|
-
bcmdx/tasks/debian.py,sha256=B9aMIIct3vNqMJr5hTr1GegXVf20H49C27FMvRRGIzI,3004
|
|
12
|
-
bcmdx/tasks/download.py,sha256=XdZYKi8zQTNYWEgUxeTNDqPgP7IGYJkMmlDDC9u93Vk,2315
|
|
13
|
-
bcmdx/tasks/image.py,sha256=Q8ujT6tq2tt0qS0DIY87PNG2RJ1wSArt2dHVTtCuccA,12161
|
|
14
|
-
bcmdx/tasks/json.py,sha256=WWOyvcZPYaqQgp-Tkm-uIJschNMBKPKtZN3yXz_SC5s,635
|
|
15
|
-
bcmdx/tasks/lib.py,sha256=4Ge0DQe4J7hyg2S-kJQ22MDbvO-E7BoyM00etNsxowc,2751
|
|
16
|
-
bcmdx/tasks/math.py,sha256=xbl5UdaDMyAjiLodDPleP4Cutrk2S3NOAgurzAgOEAE,2862
|
|
17
|
-
bcmdx/tasks/mirror.py,sha256=nAe8NYftMKzht16MFBj7RqXwvVhR6Jh2uuAyJLh87og,1098
|
|
18
|
-
bcmdx/tasks/pdf.py,sha256=fkHRgxqzrRxdb4_-9pL9wp2roqAHJPS_dVqAGJvRUsM,1504
|
|
19
|
-
bcmdx/tasks/proxy.py,sha256=xvxN5PClUnc5LQpmq2Wug7_LUVpJboMWLXBvL9lX7EM,1552
|
|
20
|
-
bcmdx/tasks/time.py,sha256=ZiqA1jdgl-TBtFSOxxP51nwv4g9iZItmkFKpf9MKelk,2453
|
|
21
|
-
bcmdx/tasks/upgrade.py,sha256=z9Ein8U_Co7fsijPxyDFM8tXBFCp4bWATImwwwvC5ho,536
|
|
22
|
-
bcmdx/tasks/wasabi.py,sha256=xWFAxprSIlBqDDMGaNXZFb-SahnW1d_R9XxSKRYIhnM,3110
|
|
23
|
-
bcmdx/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
-
bcmdx/utils/tkUtil.py,sha256=Juj0be7i3wCBIZBFic4ye76ZfsVLkFiw50J266e5-0U,4685
|
|
25
|
-
bcmd-0.6.5.dist-info/METADATA,sha256=O89LvsZh4ht3n6a15QOaAqzN6_eJd5PKZ1QnS3FOjNU,812
|
|
26
|
-
bcmd-0.6.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
27
|
-
bcmd-0.6.5.dist-info/entry_points.txt,sha256=p7bvtyio57ADkhcYAQeVus3iwXlSlcUwmuLtyR2qslQ,40
|
|
28
|
-
bcmd-0.6.5.dist-info/top_level.txt,sha256=rFHAQCIe-D-4iY7llhCS6cRDYfRIhHh9knGydcCiQjQ,6
|
|
29
|
-
bcmd-0.6.5.dist-info/RECORD,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
bcmdx
|
bcmdx/common/password.py
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import getpass
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
from async_lru import alru_cache
|
|
5
|
-
from beni import bcrypto
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@alru_cache
|
|
9
|
-
async def getPypi() -> tuple[str, str]:
|
|
10
|
-
content = 'QbuF2mV/lqovtF5dskZGD7qHknYbNuF2QseWRtWxLZTPrC/jL1tcxV8JEKaRjLsu46PxJZ7zepJwggnUTIWnEAoV5VtgP2/hbuzxxHha8817kR5c65H9fXm8eOal7DYXsUoGPQMnm59UWNXUKjmIaP4sn9nySFlRYqa8sEZSbYQ4N0NL35Dpj1e3wyQxJ+7h2jwKAz50Hh8G4yAM3/js9+NUe4ymts+UXcwsP3ADIBMkzjnFc0lEYg2d+fw0A74XWCvoZPoGqHZR/THUOVNAYxoGgDzP4SPIk1XsmtpxvfO/DpJd/Cg/0fB3MYagGKI1+m6Bxqhvd1I/lf0YbM5y4E4='
|
|
11
|
-
data = _getData(content)
|
|
12
|
-
return data['username'], data['password']
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@alru_cache
|
|
16
|
-
async def getQiniu() -> tuple[str, str]:
|
|
17
|
-
content = '7xOuA0FPCndTWcWmWLbqklQTqLTAhuEw9CarRTBYhWQ/g8wPxktw6VAiu50TLv49D1L8oCVfGafsowYDZw/prF6NQwCluPcCMy5JfdC9sKauvuZa51Nsf6PTR1UIyU8ZLUSzH+Ec2Ufcz/yAZCrcAtn63zMHNu3tTAVcZNPL597lSHdSRkpmDR8CaoUh/raH/Q=='
|
|
18
|
-
data = _getData(content)
|
|
19
|
-
return data['ak'], data['sk']
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _getData(content: str) -> dict[str, Any]:
|
|
23
|
-
index = content.find(' ')
|
|
24
|
-
if index > -1:
|
|
25
|
-
tips = f'请输入密码({content[:index]}):'
|
|
26
|
-
else:
|
|
27
|
-
tips = '请输入密码:'
|
|
28
|
-
while True:
|
|
29
|
-
try:
|
|
30
|
-
pwd = getpass.getpass(tips)
|
|
31
|
-
return bcrypto.decryptJson(content, pwd)
|
|
32
|
-
except KeyboardInterrupt:
|
|
33
|
-
raise Exception('操作取消')
|
|
34
|
-
except BaseException:
|
|
35
|
-
pass
|
bcmdx/utils/tkUtil.py
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import tkinter as tk
|
|
2
|
-
from tkinter.scrolledtext import ScrolledText
|
|
3
|
-
from typing import Callable
|
|
4
|
-
from uuid import uuid4
|
|
5
|
-
|
|
6
|
-
# version 2026-0621
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TkForm(tk.Tk):
|
|
10
|
-
|
|
11
|
-
_row = -1
|
|
12
|
-
_initFocusItem: tk.Widget | None = None
|
|
13
|
-
|
|
14
|
-
RADIO_NOTHING = uuid4().hex
|
|
15
|
-
|
|
16
|
-
def __init__(self):
|
|
17
|
-
super().__init__()
|
|
18
|
-
self.resizable(False, False)
|
|
19
|
-
self.bind("<Map>", self._onMap)
|
|
20
|
-
|
|
21
|
-
def _onMap(self, event: tk.Event):
|
|
22
|
-
if self._initFocusItem:
|
|
23
|
-
self._initFocusItem.focus_set()
|
|
24
|
-
self._initFocusItem = None
|
|
25
|
-
|
|
26
|
-
def add(self, desc: str, widget: tk.Widget):
|
|
27
|
-
self._row += 1
|
|
28
|
-
tk.Label(text=desc).grid(row=self._row, column=0, padx=10, pady=5, sticky='n')
|
|
29
|
-
widget.grid(row=self._row, column=1, padx=10, pady=5, sticky='w')
|
|
30
|
-
|
|
31
|
-
def addFrame(self, frame: tk.Frame):
|
|
32
|
-
self._row += 1
|
|
33
|
-
frame.grid(row=self._row, column=0, columnspan=2, padx=10, pady=5)
|
|
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 addLabel(self, desc: str, text: str):
|
|
52
|
-
self.add(desc, tk.Label(text=text))
|
|
53
|
-
|
|
54
|
-
def addBtn(self, label: str, command: Callable[..., None], *, width: int = 20, focus: bool = False):
|
|
55
|
-
frame = tk.Frame(self)
|
|
56
|
-
self.addFrame(frame)
|
|
57
|
-
btn = tk.Button(frame, text=label, width=width, command=command)
|
|
58
|
-
btn.pack(side="left", expand=True, padx=15)
|
|
59
|
-
if focus:
|
|
60
|
-
self._initFocusItem = btn
|
|
61
|
-
|
|
62
|
-
def addRadioBtn(self, desc: str, selectionList: list[str], *, selectedIndex: int | None = None, focusIndex: int | None = None):
|
|
63
|
-
frame = tk.Frame()
|
|
64
|
-
self.add(desc, frame)
|
|
65
|
-
var = tk.StringVar(value=selectionList[selectedIndex] if selectedIndex is not None else self.RADIO_NOTHING)
|
|
66
|
-
radioBtnList: list[tk.Radiobutton] = []
|
|
67
|
-
for version in selectionList:
|
|
68
|
-
radioBtn = tk.Radiobutton(frame, text=version, variable=var, value=version)
|
|
69
|
-
radioBtn.pack(side="left", padx=(0, 15))
|
|
70
|
-
radioBtnList.append(radioBtn)
|
|
71
|
-
if focusIndex is not None:
|
|
72
|
-
self._initFocusItem = radioBtnList[focusIndex]
|
|
73
|
-
return var
|
|
74
|
-
|
|
75
|
-
def addEntry(self, desc: str, text: str = '', *, width: int = 60, focus: bool = False):
|
|
76
|
-
entry = tk.Entry(self, width=30)
|
|
77
|
-
entry.insert(0, text)
|
|
78
|
-
self.add(desc, entry)
|
|
79
|
-
if focus:
|
|
80
|
-
self._initFocusItem = entry
|
|
81
|
-
return entry
|
|
82
|
-
|
|
83
|
-
def addScrolledText(self, desc: str, text: str = '', *, width: int = 60, height: int = 20, focus: bool = False):
|
|
84
|
-
scrolledText = ScrolledText(self, width=width, height=height)
|
|
85
|
-
scrolledText.insert("1.0", text)
|
|
86
|
-
self.add(desc, scrolledText)
|
|
87
|
-
if focus:
|
|
88
|
-
self._initFocusItem = scrolledText
|
|
89
|
-
return scrolledText
|
|
90
|
-
|
|
91
|
-
def addCheckBox(self, text: str, value: bool = False):
|
|
92
|
-
remember_var = tk.BooleanVar(value=value)
|
|
93
|
-
check_btn = tk.Checkbutton(text=text, variable=remember_var)
|
|
94
|
-
self.add("", check_btn)
|
|
95
|
-
return remember_var
|
|
96
|
-
|
|
97
|
-
def addPasswordInput(self, desc: str, *, width: int = 60, command: Callable[..., None] | None = None):
|
|
98
|
-
password_entry = tk.Entry(self, show="*", width=width) # 使用 show="*" 隐藏输入内容
|
|
99
|
-
if command:
|
|
100
|
-
password_entry.bind('<Return>', lambda event: command())
|
|
101
|
-
self.add(desc, password_entry)
|
|
102
|
-
return password_entry
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
''' 例子
|
|
106
|
-
app = TkForm()
|
|
107
|
-
app.title('更新版本信息')
|
|
108
|
-
app.addLabel('当前版本号', '3.11.8')
|
|
109
|
-
app.addLabel('上次更新时间', '2025-06-16 16:00:00')
|
|
110
|
-
version_var = app.addRadioBtn(
|
|
111
|
-
'请选择新版本',
|
|
112
|
-
['3.11.8', '3.11.9', '3.11.10'],
|
|
113
|
-
2,
|
|
114
|
-
)
|
|
115
|
-
log_text = app.addScrolledText('更新日志', '123\n23454asd这个')
|
|
116
|
-
vara = app.addCheckBox('记住密码', False)
|
|
117
|
-
varb = app.addCheckBox('阿萨德法师大润发', True)
|
|
118
|
-
passwordInput = app.addPasswordInput('密码', command=lambda: onBtn())
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def onBtn():
|
|
122
|
-
print(version_var.get())
|
|
123
|
-
print(log_text.get('1.0', "end-1c").split('\n'))
|
|
124
|
-
app.destroy()
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
app.addBtn('确定', onBtn)
|
|
128
|
-
app.bind("<Visibility>", lambda e: passwordInput.focus_set())
|
|
129
|
-
app.run()
|
|
130
|
-
'''
|
|
File without changes
|
|
File without changes
|
{bcmdx → bcmd}/common/func.py
RENAMED
|
File without changes
|
|
File without changes
|
{bcmdx → bcmd}/tasks/__init__.py
RENAMED
|
File without changes
|
{bcmdx → bcmd}/tasks/code.py
RENAMED
|
File without changes
|
{bcmdx → bcmd}/tasks/crypto.py
RENAMED
|
File without changes
|
{bcmdx → bcmd}/tasks/debian.py
RENAMED
|
File without changes
|
{bcmdx → bcmd}/tasks/download.py
RENAMED
|
File without changes
|
{bcmdx → bcmd}/tasks/json.py
RENAMED
|
File without changes
|
{bcmdx → bcmd}/tasks/math.py
RENAMED
|
File without changes
|
{bcmdx → bcmd}/tasks/mirror.py
RENAMED
|
File without changes
|
{bcmdx → bcmd}/tasks/pdf.py
RENAMED
|
File without changes
|
{bcmdx → bcmd}/tasks/proxy.py
RENAMED
|
File without changes
|
{bcmdx → bcmd}/tasks/time.py
RENAMED
|
File without changes
|
{bcmdx → bcmd}/tasks/upgrade.py
RENAMED
|
File without changes
|
{bcmdx → bcmd}/tasks/wasabi.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bcmdx/utils → test}/__init__.py
RENAMED
|
File without changes
|