pyxllib 0.0.43__py3-none-any.whl → 0.3.197__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.
- pyxllib/__init__.py +9 -2
- pyxllib/algo/__init__.py +8 -0
- pyxllib/algo/disjoint.py +54 -0
- pyxllib/algo/geo.py +541 -0
- pyxllib/{util/mathlib.py → algo/intervals.py} +172 -36
- pyxllib/algo/matcher.py +389 -0
- pyxllib/algo/newbie.py +166 -0
- pyxllib/algo/pupil.py +629 -0
- pyxllib/algo/shapelylib.py +67 -0
- pyxllib/algo/specialist.py +241 -0
- pyxllib/algo/stat.py +494 -0
- pyxllib/algo/treelib.py +149 -0
- pyxllib/algo/unitlib.py +66 -0
- pyxllib/autogui/__init__.py +5 -0
- pyxllib/autogui/activewin.py +246 -0
- pyxllib/autogui/all.py +9 -0
- pyxllib/autogui/autogui.py +852 -0
- pyxllib/autogui/uiautolib.py +362 -0
- pyxllib/autogui/virtualkey.py +102 -0
- pyxllib/autogui/wechat.py +827 -0
- pyxllib/autogui/wechat_msg.py +421 -0
- pyxllib/autogui/wxautolib.py +84 -0
- pyxllib/cv/__init__.py +1 -11
- pyxllib/cv/expert.py +267 -0
- pyxllib/cv/{imlib.py → imfile.py} +18 -83
- pyxllib/cv/imhash.py +39 -0
- pyxllib/cv/pupil.py +9 -0
- pyxllib/cv/rgbfmt.py +1525 -0
- pyxllib/cv/slidercaptcha.py +137 -0
- pyxllib/cv/trackbartools.py +163 -49
- pyxllib/cv/xlcvlib.py +1040 -0
- pyxllib/cv/xlpillib.py +423 -0
- pyxllib/data/__init__.py +0 -0
- pyxllib/data/echarts.py +240 -0
- pyxllib/data/jsonlib.py +89 -0
- pyxllib/{util/oss2_.py → data/oss.py} +11 -9
- pyxllib/data/pglib.py +1127 -0
- pyxllib/data/sqlite.py +568 -0
- pyxllib/{util → data}/sqllib.py +13 -31
- pyxllib/ext/JLineViewer.py +505 -0
- pyxllib/ext/__init__.py +6 -0
- pyxllib/{util → ext}/demolib.py +119 -35
- pyxllib/ext/drissionlib.py +277 -0
- pyxllib/ext/kq5034lib.py +12 -0
- pyxllib/{util/main.py → ext/old.py} +122 -284
- pyxllib/ext/qt.py +449 -0
- pyxllib/ext/robustprocfile.py +497 -0
- pyxllib/ext/seleniumlib.py +76 -0
- pyxllib/{util/tklib.py → ext/tk.py} +10 -11
- pyxllib/ext/unixlib.py +827 -0
- pyxllib/ext/utools.py +351 -0
- pyxllib/{util/webhooklib.py → ext/webhook.py} +45 -17
- pyxllib/ext/win32lib.py +40 -0
- pyxllib/ext/wjxlib.py +88 -0
- pyxllib/ext/wpsapi.py +124 -0
- pyxllib/ext/xlwork.py +9 -0
- pyxllib/ext/yuquelib.py +1105 -0
- pyxllib/file/__init__.py +17 -0
- pyxllib/file/docxlib.py +761 -0
- pyxllib/{util → file}/gitlib.py +40 -27
- pyxllib/file/libreoffice.py +165 -0
- pyxllib/file/movielib.py +148 -0
- pyxllib/file/newbie.py +10 -0
- pyxllib/file/onenotelib.py +1469 -0
- pyxllib/file/packlib/__init__.py +330 -0
- pyxllib/{util → file/packlib}/zipfile.py +598 -195
- pyxllib/file/pdflib.py +426 -0
- pyxllib/file/pupil.py +185 -0
- pyxllib/file/specialist/__init__.py +685 -0
- pyxllib/{basic/_5_dirlib.py → file/specialist/dirlib.py} +364 -93
- pyxllib/file/specialist/download.py +193 -0
- pyxllib/file/specialist/filelib.py +2829 -0
- pyxllib/file/xlsxlib.py +3131 -0
- pyxllib/file/xlsyncfile.py +341 -0
- pyxllib/prog/__init__.py +5 -0
- pyxllib/prog/cachetools.py +64 -0
- pyxllib/prog/deprecatedlib.py +233 -0
- pyxllib/prog/filelock.py +42 -0
- pyxllib/prog/ipyexec.py +253 -0
- pyxllib/prog/multiprogs.py +940 -0
- pyxllib/prog/newbie.py +451 -0
- pyxllib/prog/pupil.py +1197 -0
- pyxllib/{sitepackages.py → prog/sitepackages.py} +5 -3
- pyxllib/prog/specialist/__init__.py +391 -0
- pyxllib/prog/specialist/bc.py +203 -0
- pyxllib/prog/specialist/browser.py +497 -0
- pyxllib/prog/specialist/common.py +347 -0
- pyxllib/prog/specialist/datetime.py +199 -0
- pyxllib/prog/specialist/tictoc.py +240 -0
- pyxllib/prog/specialist/xllog.py +180 -0
- pyxllib/prog/xlosenv.py +108 -0
- pyxllib/stdlib/__init__.py +17 -0
- pyxllib/{util → stdlib}/tablepyxl/__init__.py +1 -3
- pyxllib/{util → stdlib}/tablepyxl/style.py +1 -1
- pyxllib/{util → stdlib}/tablepyxl/tablepyxl.py +2 -4
- pyxllib/text/__init__.py +8 -0
- pyxllib/text/ahocorasick.py +39 -0
- pyxllib/text/airscript.js +744 -0
- pyxllib/text/charclasslib.py +121 -0
- pyxllib/text/jiebalib.py +267 -0
- pyxllib/text/jinjalib.py +32 -0
- pyxllib/text/jsa_ai_prompt.md +271 -0
- pyxllib/text/jscode.py +922 -0
- pyxllib/text/latex/__init__.py +158 -0
- pyxllib/text/levenshtein.py +303 -0
- pyxllib/text/nestenv.py +1215 -0
- pyxllib/text/newbie.py +300 -0
- pyxllib/text/pupil/__init__.py +8 -0
- pyxllib/text/pupil/common.py +1121 -0
- pyxllib/text/pupil/xlalign.py +326 -0
- pyxllib/text/pycode.py +47 -0
- pyxllib/text/specialist/__init__.py +8 -0
- pyxllib/text/specialist/common.py +112 -0
- pyxllib/text/specialist/ptag.py +186 -0
- pyxllib/text/spellchecker.py +172 -0
- pyxllib/text/templates/echart_base.html +11 -0
- pyxllib/text/templates/highlight_code.html +17 -0
- pyxllib/text/templates/latex_editor.html +103 -0
- pyxllib/text/vbacode.py +17 -0
- pyxllib/text/xmllib.py +747 -0
- pyxllib/xl.py +39 -0
- pyxllib/xlcv.py +17 -0
- pyxllib-0.3.197.dist-info/METADATA +48 -0
- pyxllib-0.3.197.dist-info/RECORD +126 -0
- {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info}/WHEEL +4 -5
- pyxllib/basic/_1_strlib.py +0 -945
- pyxllib/basic/_2_timelib.py +0 -488
- pyxllib/basic/_3_pathlib.py +0 -916
- pyxllib/basic/_4_loglib.py +0 -419
- pyxllib/basic/__init__.py +0 -54
- pyxllib/basic/arrow_.py +0 -250
- pyxllib/basic/chardet_.py +0 -66
- pyxllib/basic/dirlib.py +0 -529
- pyxllib/basic/dprint.py +0 -202
- pyxllib/basic/extension.py +0 -12
- pyxllib/basic/judge.py +0 -31
- pyxllib/basic/log.py +0 -204
- pyxllib/basic/pathlib_.py +0 -705
- pyxllib/basic/pytictoc.py +0 -102
- pyxllib/basic/qiniu_.py +0 -61
- pyxllib/basic/strlib.py +0 -761
- pyxllib/basic/timer.py +0 -132
- pyxllib/cv/cv.py +0 -834
- pyxllib/cv/cvlib/_1_geo.py +0 -543
- pyxllib/cv/cvlib/_2_cvprcs.py +0 -309
- pyxllib/cv/cvlib/_2_imgproc.py +0 -594
- pyxllib/cv/cvlib/_3_pilprcs.py +0 -80
- pyxllib/cv/cvlib/_4_cvimg.py +0 -211
- pyxllib/cv/cvlib/__init__.py +0 -10
- pyxllib/cv/debugtools.py +0 -82
- pyxllib/cv/fitz_.py +0 -300
- pyxllib/cv/installer.py +0 -42
- pyxllib/debug/_0_installer.py +0 -38
- pyxllib/debug/_1_typelib.py +0 -277
- pyxllib/debug/_2_chrome.py +0 -198
- pyxllib/debug/_3_showdir.py +0 -161
- pyxllib/debug/_4_bcompare.py +0 -140
- pyxllib/debug/__init__.py +0 -49
- pyxllib/debug/bcompare.py +0 -132
- pyxllib/debug/chrome.py +0 -198
- pyxllib/debug/installer.py +0 -38
- pyxllib/debug/showdir.py +0 -158
- pyxllib/debug/typelib.py +0 -278
- pyxllib/image/__init__.py +0 -12
- pyxllib/torch/__init__.py +0 -20
- pyxllib/torch/modellib.py +0 -37
- pyxllib/torch/trainlib.py +0 -344
- pyxllib/util/__init__.py +0 -20
- pyxllib/util/aip_.py +0 -141
- pyxllib/util/casiadb.py +0 -59
- pyxllib/util/excellib.py +0 -495
- pyxllib/util/filelib.py +0 -612
- pyxllib/util/jsondata.py +0 -27
- pyxllib/util/jsondata2.py +0 -92
- pyxllib/util/labelmelib.py +0 -139
- pyxllib/util/onepy/__init__.py +0 -29
- pyxllib/util/onepy/onepy.py +0 -574
- pyxllib/util/onepy/onmanager.py +0 -170
- pyxllib/util/pyautogui_.py +0 -219
- pyxllib/util/textlib.py +0 -1305
- pyxllib/util/unorder.py +0 -22
- pyxllib/util/xmllib.py +0 -639
- pyxllib-0.0.43.dist-info/METADATA +0 -39
- pyxllib-0.0.43.dist-info/RECORD +0 -80
- pyxllib-0.0.43.dist-info/top_level.txt +0 -1
- {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info/licenses}/LICENSE +0 -0
pyxllib/ext/utools.py
ADDED
@@ -0,0 +1,351 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# @Author : 陈坤泽
|
4
|
+
# @Email : 877362867@qq.com
|
5
|
+
# @Date : 2021/04/04 17:03
|
6
|
+
|
7
|
+
""" 专门给utools的快捷命令扩展的一系列python工具库
|
8
|
+
"""
|
9
|
+
from pyxllib.prog.pupil import check_install_package
|
10
|
+
|
11
|
+
check_install_package('fire')
|
12
|
+
check_install_package('humanfriendly')
|
13
|
+
check_install_package('pandas')
|
14
|
+
check_install_package('pyautogui', 'PyAutoGui') # 其实pip install不区分大小写,不过官方这里安装是驼峰名
|
15
|
+
|
16
|
+
import datetime
|
17
|
+
import json
|
18
|
+
import os
|
19
|
+
import pathlib
|
20
|
+
import pyperclip
|
21
|
+
import re
|
22
|
+
|
23
|
+
import fire
|
24
|
+
from humanfriendly import format_timespan
|
25
|
+
import pandas as pd
|
26
|
+
import pyautogui
|
27
|
+
|
28
|
+
from pyxllib.prog.specialist import browser, TicToc, parse_datetime
|
29
|
+
from pyxllib.file.specialist import XlPath
|
30
|
+
from pyxllib.autogui.autogui import type_text, clipboard_decorator
|
31
|
+
|
32
|
+
|
33
|
+
def _print_df_result(df, outfmt='text'):
|
34
|
+
# subinput可以强制重置输出类型
|
35
|
+
# TODO argparser可以对str类型进行参数解析?
|
36
|
+
# if 'browser' in kwargs['subinput']:
|
37
|
+
# outfmt = 'browser'
|
38
|
+
# elif 'html' in kwargs['subinput']:
|
39
|
+
# outfmt = 'html'
|
40
|
+
# elif 'text' in kwargs['subinput']:
|
41
|
+
# outfmt = 'text'
|
42
|
+
|
43
|
+
if outfmt == 'html':
|
44
|
+
# content = df.to_html().replace('–', '-') # 很多app会出现一个utf8特殊的-,转gbk编码会出错+
|
45
|
+
try: # utools里按gbk会有很多编码问题,如果不能直接显示,就打开浏览器看
|
46
|
+
print(df.to_html())
|
47
|
+
except UnicodeEncodeError:
|
48
|
+
browser(df)
|
49
|
+
elif outfmt == 'browser':
|
50
|
+
browser(df)
|
51
|
+
else:
|
52
|
+
with pd.option_context('display.max_colwidth', -1, 'display.max_columns', 20,
|
53
|
+
'display.width', 200): # 上下文控制格式
|
54
|
+
print(df)
|
55
|
+
|
56
|
+
|
57
|
+
class UtoolsBase:
|
58
|
+
def __init__(self, cmds, *, outfmt='text'):
|
59
|
+
"""
|
60
|
+
:param cmds: "快捷命令"响应的命令集,即调用出可传入的输入文件、所在窗口、选中文件等信息
|
61
|
+
:param outfmt: 输出格式
|
62
|
+
text,纯文本
|
63
|
+
html,html格式
|
64
|
+
browser,用浏览器打开
|
65
|
+
"""
|
66
|
+
# 1 删除没有值的键
|
67
|
+
self.cmds = dict()
|
68
|
+
for k, v in cmds.items():
|
69
|
+
if cmds[k] not in ('{{' + k + '}}', ''):
|
70
|
+
self.cmds[k] = v
|
71
|
+
|
72
|
+
# 2 「快捷命令」的ClipText宏展开,如果存在连续3个单引号,会跟py字符串定义冲突,产生bug
|
73
|
+
# 所以这段不建议用宏展开,这里该类会自动调用pyperclip获取
|
74
|
+
# 这个可以作为可选功能关闭,但实验证明这个对性能其实没有太大影响
|
75
|
+
if 'ClipText' not in self.cmds:
|
76
|
+
self.cmds['ClipText'] = pyperclip.paste().replace('\r\n', '\n')
|
77
|
+
|
78
|
+
# 3 解析json数据
|
79
|
+
# 解析window模式的WindowInfo
|
80
|
+
if 'WindowInfo' in self.cmds:
|
81
|
+
self.cmds['WindowInfo'] = json.loads(self.cmds['WindowInfo'].encode('utf8'))
|
82
|
+
|
83
|
+
# 解析files模式的MatchedFiles
|
84
|
+
if 'MatchedFiles' in self.cmds:
|
85
|
+
# 右边引用没写错。因为MatchedFiles本身获取内容不稳定,可能会有bug,取更稳定的payload来初始化MatchedFiles
|
86
|
+
self.cmds['MatchedFiles'] = json.loads(self.cmds['payload'].encode('utf8'))
|
87
|
+
# 在有些软件中,比如everything,是可以选中多个不同目录的文件的。。。所以严格来说不能直接按第一个文件算pwd
|
88
|
+
# 但这个讯息是可以写一个工具分析的
|
89
|
+
# self.cmds['pwd'] = os.path.dirname(self.cmds['MatchedFiles'][0]['path'])
|
90
|
+
|
91
|
+
# TODO 如果选中的文件全部是同一个目录下的,要存储pwd
|
92
|
+
dirs = {os.path.dirname(f['path']) for f in self.cmds['MatchedFiles']}
|
93
|
+
if len(dirs) == 1:
|
94
|
+
self.cmds['pwd'] = list(dirs)[0]
|
95
|
+
|
96
|
+
# 4 当前工作目录
|
97
|
+
# 要根据场景切换pwd
|
98
|
+
# files默认工作目录是app: C:\Users\chen\AppData\Local\Programs\utools
|
99
|
+
# window默认工作目录是exe
|
100
|
+
# window模式存储的pwd跟各种软件有关
|
101
|
+
# explorer就是当前窗口,没问题
|
102
|
+
# onenote存的是桌面
|
103
|
+
if 'pwd' in self.cmds:
|
104
|
+
os.chdir(self.cmds['pwd'])
|
105
|
+
|
106
|
+
# 5 subinput可以用字典结构扩展参数
|
107
|
+
if 'subinput' in self.cmds:
|
108
|
+
if re.match(r'\{.+\}$', self.cmds['subinput']):
|
109
|
+
ext_kwargs = eval(self.cmds['subinput'])
|
110
|
+
self.cmds.update(ext_kwargs)
|
111
|
+
else:
|
112
|
+
self.cmds['subinput'] = ''
|
113
|
+
|
114
|
+
self.outfmt = self.cmds['outfmt'] if 'outfmt' in self.cmds else outfmt
|
115
|
+
|
116
|
+
|
117
|
+
class UtoolsName(UtoolsBase):
|
118
|
+
|
119
|
+
def check_cmds(self):
|
120
|
+
""" 显示所有参数值 """
|
121
|
+
df = pd.DataFrame.from_records([(k, v) for k, v in self.cmds.items()],
|
122
|
+
columns=['key', 'content'])
|
123
|
+
_print_df_result(df, self.outfmt)
|
124
|
+
|
125
|
+
def bcompare(self):
|
126
|
+
""" 可以通过subinput设置文件后缀类型 """
|
127
|
+
from pyxllib.prog.specialist import bcompare
|
128
|
+
|
129
|
+
suffix = self.cmds.get('subinput', None)
|
130
|
+
try:
|
131
|
+
f1 = XlPath.init('left', XlPath.tempdir(), suffix=suffix)
|
132
|
+
f2 = XlPath.init('right', XlPath.tempdir(), suffix=suffix)
|
133
|
+
except OSError: # 忽略错误的扩展名
|
134
|
+
f1 = XlPath.init('left', XlPath.tempdir())
|
135
|
+
f2 = XlPath.init('right', XlPath.tempdir())
|
136
|
+
f1.write_text(self.cmds['ClipText'])
|
137
|
+
f2.write_text(self.cmds['ClipText'] + '\n')
|
138
|
+
bcompare(f1, f2, wait=False)
|
139
|
+
|
140
|
+
@clipboard_decorator(copy=False, typing=True)
|
141
|
+
def common_dir(self):
|
142
|
+
# 目录路径生成工具
|
143
|
+
from pyxlpr.data.datasets import common_path
|
144
|
+
|
145
|
+
def func(name, unix_path=False):
|
146
|
+
p = getattr(common_path, name)
|
147
|
+
|
148
|
+
if unix_path:
|
149
|
+
# 因为用了符号链接,实际位置会变回D:/slns,这里需要反向替换下
|
150
|
+
p = str(p).replace('D:/slns', 'D:/home/chenkunze/slns')
|
151
|
+
p = str(p)[2:]
|
152
|
+
else:
|
153
|
+
# slns比较特别,本地要重定向到D:/slns
|
154
|
+
p = str(p).replace('D:/home/chenkunze/slns', 'D:/slns')
|
155
|
+
p = p.replace('/', '\\')
|
156
|
+
|
157
|
+
return p
|
158
|
+
|
159
|
+
return fire.Fire(func, self.cmds['subinput'], 'CommonDir')
|
160
|
+
|
161
|
+
def wdate(self):
|
162
|
+
""" week dates 输入一周的简略日期值 """
|
163
|
+
|
164
|
+
def func(start):
|
165
|
+
weektag = '一二三四五六日'
|
166
|
+
for i in range(7):
|
167
|
+
dt = parse_datetime(start) + datetime.timedelta(i)
|
168
|
+
type_text(dt.strftime('%y%m%d') + f'周{weektag[dt.weekday()]}')
|
169
|
+
pyautogui.press('down')
|
170
|
+
|
171
|
+
fire.Fire(func, self.cmds['subinput'], 'wdate')
|
172
|
+
|
173
|
+
def input_digits(self):
|
174
|
+
""" 输入数字 """
|
175
|
+
|
176
|
+
def func(start, stop, step=1):
|
177
|
+
for i in range(start, stop, step):
|
178
|
+
pyautogui.write(str(i))
|
179
|
+
pyautogui.press('down')
|
180
|
+
|
181
|
+
fire.Fire(func, self.cmds['subinput'], 'input_digits')
|
182
|
+
|
183
|
+
def __win32(self):
|
184
|
+
""" win32相关自动化
|
185
|
+
|
186
|
+
目前主要是word自动化相关的功能,这里有很多demo可以学习怎么用win32做word的自动化
|
187
|
+
"""
|
188
|
+
pass
|
189
|
+
|
190
|
+
def browser(self):
|
191
|
+
""" 将内容复制到word,另存为html文件后,用浏览器打开查看 """
|
192
|
+
from pyxllib.file.docxlib import rebuild_document_by_word
|
193
|
+
|
194
|
+
file = fire.Fire(rebuild_document_by_word, self.cmds['subinput'], 'browser')
|
195
|
+
browser(file)
|
196
|
+
|
197
|
+
def create_text_image(self):
|
198
|
+
from pyxllib.xl import XlPath
|
199
|
+
from pyxllib.xlcv import xlpil, create_text_image
|
200
|
+
|
201
|
+
@clipboard_decorator('html')
|
202
|
+
def func(text, font_size=20, *, size=None, xy=None, bg_color=None, text_color=None):
|
203
|
+
im = create_text_image(text, size, xy=xy, font_size=font_size, bg_color=bg_color, text_color=text_color)
|
204
|
+
# 这种只能语雀里用
|
205
|
+
data = xlpil.to_buffer(im, b64encode=True).decode()
|
206
|
+
res = '<img src="data:image/jpg;base64,' + data + '"/>'
|
207
|
+
# 这种只能onenote、qq、微信里用
|
208
|
+
p = XlPath.tempdir() / 'create_text_image.jpg'
|
209
|
+
im.save(p)
|
210
|
+
res += f'<img src="{p}"/>'
|
211
|
+
# 那么我把两种都复制,就能兼容多种场景了~~
|
212
|
+
return res
|
213
|
+
|
214
|
+
fire.Fire(func, self.cmds['subinput'], 'create_text_image')
|
215
|
+
|
216
|
+
|
217
|
+
class UtoolsText(UtoolsBase):
|
218
|
+
def __init__(self, cmds, *, outfmt='text'):
|
219
|
+
super().__init__(cmds, outfmt=outfmt)
|
220
|
+
|
221
|
+
@clipboard_decorator(paste=True)
|
222
|
+
def coderegex(self):
|
223
|
+
# tt = TicToc()
|
224
|
+
s = self.cmds['ClipText']
|
225
|
+
return eval(self.cmds['subinput'])
|
226
|
+
# print(f'finished in {format_timespan(tt.tocvalue())}.')
|
227
|
+
|
228
|
+
def refine_text(self, func, rtype='text', *, copy=True, paste=True, typing=False):
|
229
|
+
from functools import partial
|
230
|
+
|
231
|
+
@clipboard_decorator(rtype=rtype, copy=copy, paste=paste, typing=typing)
|
232
|
+
def _refine():
|
233
|
+
return fire.Fire(partial(func, self.cmds['ClipText']),
|
234
|
+
self.cmds['subinput'], func.__name__)
|
235
|
+
|
236
|
+
return _refine()
|
237
|
+
|
238
|
+
def bc_text(self, func):
|
239
|
+
""" 用bc软件打开,对比前后文本变化,有用户自己决定如何处理保留 """
|
240
|
+
from functools import partial
|
241
|
+
from pyxllib.prog.specialist import bcompare
|
242
|
+
|
243
|
+
text1 = self.cmds['ClipText']
|
244
|
+
text2 = fire.Fire(partial(func, text1), self.cmds['subinput'], func.__name__)
|
245
|
+
bcompare(text1, text2)
|
246
|
+
|
247
|
+
|
248
|
+
class UtoolsFile(UtoolsBase):
|
249
|
+
""" 文件相关操作工具 """
|
250
|
+
|
251
|
+
def __init__(self, cmds, *, outfmt='text'):
|
252
|
+
super().__init__(cmds, outfmt=outfmt)
|
253
|
+
|
254
|
+
# 如果是window等模式,补充 MatchedFiles
|
255
|
+
if 'MatchedFiles' in self.cmds:
|
256
|
+
self.paths = [pathlib.Path(f['path']) for f in self.cmds['MatchedFiles']]
|
257
|
+
else:
|
258
|
+
self.paths = [pathlib.Path(os.path.abspath(f)).resolve() for f in os.listdir('../robot')]
|
259
|
+
|
260
|
+
@classmethod
|
261
|
+
def is_image(cls, f):
|
262
|
+
return bool(re.match('jpe?g|png|gif|bmp', f.suffix.lower()[1:]))
|
263
|
+
|
264
|
+
def codefile(self):
|
265
|
+
""" 多用途文件处理器
|
266
|
+
|
267
|
+
核心理念是不设定具体功能,而是让用户在 subinput 自己写需要执行的py代码功能
|
268
|
+
而在subinput,可以用一些特殊的标识符来传参
|
269
|
+
|
270
|
+
以file为例,有4类参数:
|
271
|
+
file,处理当前目录 文件
|
272
|
+
rfile,递归处理所有目录下 文件
|
273
|
+
files,当前目录 所有文件
|
274
|
+
rfiles,递归获取所有目录下 所有文件
|
275
|
+
|
276
|
+
类型上,file可以改为
|
277
|
+
dir,只分析目录
|
278
|
+
path,所有文件和目录
|
279
|
+
|
280
|
+
扩展了一些特殊的类型:
|
281
|
+
imfile,图片文件
|
282
|
+
"""
|
283
|
+
from functools import reduce
|
284
|
+
try:
|
285
|
+
from xlproject.xyz.kzconfig import KzDataSync
|
286
|
+
from pyxlpr.data.labelme import reduce_labelme_jsonfile
|
287
|
+
except ModuleNotFoundError:
|
288
|
+
pass
|
289
|
+
|
290
|
+
tt = TicToc()
|
291
|
+
|
292
|
+
# 1 获得所有标识符
|
293
|
+
# 每个单词前后都有空格,方便定界
|
294
|
+
keywords = filter(lambda x: re.match(r'[a-zA-Z_]+$', x),
|
295
|
+
set(re.findall(r'\.?[a-zA-Z_]+\(?', self.cmds['subinput'])))
|
296
|
+
keywords = ' ' + ' '.join(keywords) + ' '
|
297
|
+
# print('keywords:', keywords)
|
298
|
+
|
299
|
+
# 2 生成一些备用的智能参数
|
300
|
+
# 一级目录下选中的文件
|
301
|
+
paths = self.paths
|
302
|
+
# 用正则判断一些智能参数是否要计算,注意不能只判断files,如果出现file,也是要引用files的
|
303
|
+
files = [XlPath(p) for p in paths if p.is_file()] if re.search(r'files? ', keywords) else []
|
304
|
+
imfiles = [f for f in files if self.is_image(f)] if re.search(r' imfiles? ', keywords) else []
|
305
|
+
# 出现r系列的递归操作,都是要计算出dirs的
|
306
|
+
dirs = [XlPath(p) for p in paths if p.is_dir()] if re.search(r' (dirs?|r[a-zA-Z_]+) ', keywords) else []
|
307
|
+
|
308
|
+
# 递归所有的文件
|
309
|
+
rpaths = reduce(lambda x, y: x + y.select('**/*').subpaths(), [paths] + dirs) \
|
310
|
+
if re.search(r' (r[a-zA-Z_]+) ', keywords) else []
|
311
|
+
rfiles = [XlPath(p) for p in rpaths if p.is_file()] if re.search(r' (r(im)?files?) ', keywords) else []
|
312
|
+
rimfiles = [f for f in rfiles if self.is_image(f)] if re.search(r' rimfiles? ', keywords) else []
|
313
|
+
rdirs = [XlPath(p) for p in rpaths if p.is_dir()] if re.search(r' (rdirs?) ', keywords) else []
|
314
|
+
|
315
|
+
# 3 判断是否要智能开循环处理
|
316
|
+
m = re.search(r' (r?(path|(im)?file|dir)) ', keywords)
|
317
|
+
if m:
|
318
|
+
name = m.group(1)
|
319
|
+
objs = eval(name + 's')
|
320
|
+
print('len(' + name + 's)=', len(objs))
|
321
|
+
for x in objs:
|
322
|
+
locals()[name] = x
|
323
|
+
eval(self.cmds['subinput'])
|
324
|
+
else: # 没有的话就直接处理所有文件
|
325
|
+
eval(self.cmds['subinput'])
|
326
|
+
|
327
|
+
# 4 运行结束标志
|
328
|
+
print(f'finished in {format_timespan(tt.tocvalue())}.')
|
329
|
+
|
330
|
+
def open_jsonl(self):
|
331
|
+
""" 打开jsonl文件 """
|
332
|
+
from pyxllib.ext.JLineViewer import start_jlineviewer
|
333
|
+
start_jlineviewer(self.paths[0].as_posix())
|
334
|
+
|
335
|
+
def open_jsonl_with_excel(self):
|
336
|
+
""" 用excel打开jsonl文件 """
|
337
|
+
from pyxllib.algo.stat import write_dataframes_to_excel
|
338
|
+
|
339
|
+
file = self.paths[0].as_posix()
|
340
|
+
data = XlPath(file).read_jsonl()
|
341
|
+
|
342
|
+
file = XlPath.create_tempfile_path('.xlsx')
|
343
|
+
df = pd.DataFrame.from_dict(data)
|
344
|
+
write_dataframes_to_excel(file, {'Sheet1': df})
|
345
|
+
|
346
|
+
os.startfile(file)
|
347
|
+
|
348
|
+
|
349
|
+
if __name__ == '__main__':
|
350
|
+
with TicToc('utools'):
|
351
|
+
pass
|
@@ -2,9 +2,9 @@
|
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
# @Author : 陈坤泽
|
4
4
|
# @Email : 877362867@qq.com
|
5
|
-
# @
|
6
|
-
|
5
|
+
# @Date : 2020/10/21 09:22
|
7
6
|
|
7
|
+
import os
|
8
8
|
import time
|
9
9
|
import hmac
|
10
10
|
import hashlib
|
@@ -16,6 +16,7 @@ import requests
|
|
16
16
|
|
17
17
|
class WeixinRobot:
|
18
18
|
""" 企业微信 机器人 """
|
19
|
+
|
19
20
|
def __init__(self, url):
|
20
21
|
self.url = url
|
21
22
|
|
@@ -36,9 +37,10 @@ class DingtalkRobot:
|
|
36
37
|
https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq
|
37
38
|
"""
|
38
39
|
|
39
|
-
def __init__(self,
|
40
|
-
|
41
|
-
self.url
|
40
|
+
def __init__(self, access_token=None, secret=None):
|
41
|
+
access_token = access_token or os.getenv('DINGTALK_ROBOT_SECRET')
|
42
|
+
self.url = f'https://oapi.dingtalk.com/robot/send?access_token={access_token}'
|
43
|
+
self.url += self.add_secret(secret or os.getenv('DINGTALK_ROBOT_SECRET'))
|
42
44
|
self.headers = {"Content-Type": "application/json"}
|
43
45
|
|
44
46
|
@classmethod
|
@@ -50,23 +52,22 @@ class DingtalkRobot:
|
|
50
52
|
string_to_sign_enc = string_to_sign.encode('utf-8')
|
51
53
|
hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
|
52
54
|
sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
|
53
|
-
|
54
55
|
return f'×tamp={timestamp}&sign={sign}'
|
55
56
|
|
56
|
-
def
|
57
|
+
def send_data(self, data):
|
57
58
|
try:
|
58
59
|
requests.post(url=self.url, headers=self.headers, json=data)
|
59
|
-
except requests.exceptions.ConnectionError: # 没网发送失败的时候也不报错
|
60
|
-
|
60
|
+
except requests.exceptions.ConnectionError as e: # 没网发送失败的时候也不报错
|
61
|
+
raise e
|
61
62
|
|
62
|
-
def
|
63
|
+
def send_text(self, content):
|
63
64
|
msgtype = 'text'
|
64
65
|
d = {}
|
65
66
|
if content: d['content'] = content
|
66
67
|
data = {"msgtype": msgtype, msgtype: d}
|
67
|
-
self.
|
68
|
+
self.send_data(data)
|
68
69
|
|
69
|
-
def
|
70
|
+
def send_link(self, text='', title='', pic_url='', message_url=''):
|
70
71
|
msgtype = 'link'
|
71
72
|
d = {}
|
72
73
|
if text: d['text'] = text
|
@@ -74,18 +75,45 @@ class DingtalkRobot:
|
|
74
75
|
if pic_url: d['picUrl'] = pic_url
|
75
76
|
if message_url: d['messageUrl'] = message_url
|
76
77
|
data = {"msgtype": msgtype, msgtype: d}
|
77
|
-
self.
|
78
|
+
self.send_data(data)
|
78
79
|
|
79
|
-
def
|
80
|
+
def send_markdown(self, text='', title=''):
|
80
81
|
msgtype = 'link'
|
81
82
|
d = {}
|
82
83
|
if text: d['text'] = text
|
83
84
|
if title: d['title'] = title
|
84
85
|
data = {"msgtype": msgtype, msgtype: d}
|
85
|
-
self.
|
86
|
+
self.send_data(data)
|
86
87
|
|
87
|
-
def
|
88
|
+
def send_actioncard(self, text='', title='', siggle_url='', siggle_title='', btn_orientation='0'):
|
88
89
|
raise NotImplementedError
|
89
90
|
|
90
|
-
def
|
91
|
+
def send_feedcard(self):
|
91
92
|
raise NotImplementedError
|
93
|
+
|
94
|
+
|
95
|
+
class DingtalkRobot2(DingtalkRobot):
|
96
|
+
|
97
|
+
def __init__(self, title=None):
|
98
|
+
super().__init__()
|
99
|
+
self.title = title
|
100
|
+
|
101
|
+
def send_text2(self, text): # 增加一个更加定制化的便捷接口
|
102
|
+
from pyxllib.prog.pupil import utc_timestamp
|
103
|
+
|
104
|
+
if self.title:
|
105
|
+
self.send_text(f'{utc_timestamp()} {get_host_nickname()} [{self.title}] {text}')
|
106
|
+
else:
|
107
|
+
self.send_text(f'{utc_timestamp()} {get_host_nickname()} {text}')
|
108
|
+
|
109
|
+
def __enter__(self):
|
110
|
+
self.send_text2('启动')
|
111
|
+
return self
|
112
|
+
|
113
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
114
|
+
from pyxllib.prog.pupil import format_exception
|
115
|
+
|
116
|
+
if exc_tb is None:
|
117
|
+
self.send_text2('完成')
|
118
|
+
else:
|
119
|
+
self.send_text2(f'报错\n{format_exception(exc_val, 3)}')
|
pyxllib/ext/win32lib.py
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# @Author : 陈坤泽
|
4
|
+
# @Email : 877362867@qq.com
|
5
|
+
# @Date : 2021/09/07 10:21
|
6
|
+
|
7
|
+
|
8
|
+
import win32com.client as win32
|
9
|
+
import pythoncom
|
10
|
+
|
11
|
+
|
12
|
+
def get_win32_app(name, visible=False):
|
13
|
+
""" 启动可支持pywin32自动化处理的应用
|
14
|
+
|
15
|
+
Args:
|
16
|
+
str name: 应用名称,不区分大小写,比如word, excel, powerpoint, onenote
|
17
|
+
不带'.'的情况下,会自动添加'.Application'的后缀
|
18
|
+
visible: 应用是否可见
|
19
|
+
|
20
|
+
Returns: app
|
21
|
+
|
22
|
+
"""
|
23
|
+
# 1 name
|
24
|
+
name = name.lower()
|
25
|
+
if '.' not in name:
|
26
|
+
name += '.application'
|
27
|
+
|
28
|
+
# 2 app
|
29
|
+
# 这里可能还有些问题,不同的应用,机制不太一样,后面再细化完善吧
|
30
|
+
try:
|
31
|
+
app = win32.GetActiveObject(f'{name}') # 不能关联到普通方式打开的应用。但代码打开的应用都能找得到。
|
32
|
+
except pythoncom.com_error:
|
33
|
+
app = win32.gencache.EnsureDispatch(f'{name}')
|
34
|
+
# 还有种常见的初始化方法,是 win32com.client.Dispatch和win32com.client.dynamic.Dispatch
|
35
|
+
# from win32com.client.dynamic import Disypatch
|
36
|
+
|
37
|
+
if visible is not None:
|
38
|
+
app.Visible = visible
|
39
|
+
|
40
|
+
return app
|
pyxllib/ext/wjxlib.py
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# @Author : 陈坤泽
|
4
|
+
# @Email : 877362867@qq.com
|
5
|
+
# @Date : 2024/11/19
|
6
|
+
|
7
|
+
""" 问卷星 相关工具 """
|
8
|
+
|
9
|
+
import os
|
10
|
+
import io
|
11
|
+
import time
|
12
|
+
|
13
|
+
from loguru import logger
|
14
|
+
from DrissionPage import Chromium
|
15
|
+
import pandas as pd
|
16
|
+
|
17
|
+
from pyxllib.ext.drissionlib import DpWebBase
|
18
|
+
|
19
|
+
|
20
|
+
class WjxWeb(DpWebBase):
|
21
|
+
""" 问卷星网页的爬虫 """
|
22
|
+
|
23
|
+
def __init__(self, url=None):
|
24
|
+
super().__init__(url or 'https://www.wjx.cn')
|
25
|
+
self.login()
|
26
|
+
|
27
|
+
def login(self):
|
28
|
+
tab = self.tab
|
29
|
+
|
30
|
+
if tab.url.startswith('https://www.wjx.cn/wjx/activitystat/resultlimit.aspx'):
|
31
|
+
tab('tag:a@@text():登录').click()
|
32
|
+
|
33
|
+
if tab.url.lower().startswith('https://www.wjx.cn/login.aspx'):
|
34
|
+
tab('tag:input@@name=UserName').input(os.getenv('WJX_USERNAME'), clear=True)
|
35
|
+
tab('tag:input@@name=Password').input(os.getenv('WJX_PASSWORD'), clear=True)
|
36
|
+
tab('tag:input@@type=submit').click()
|
37
|
+
|
38
|
+
def get_page_num(self):
|
39
|
+
"""
|
40
|
+
返回当前页编号和总页数 (idx, num)。
|
41
|
+
"""
|
42
|
+
idx, num = map(int, self.tab('tag:span@@class=paging-num').text.split('/'))
|
43
|
+
return idx, num
|
44
|
+
|
45
|
+
def prev_page(self):
|
46
|
+
self.tab('tag:a@@class=go-pre').click()
|
47
|
+
|
48
|
+
def next_page(self):
|
49
|
+
self.tab('tag:a@@class=go-next').click()
|
50
|
+
|
51
|
+
def _parse_table(self):
|
52
|
+
"""处理并解析网页中的表格数据"""
|
53
|
+
table_html = self.tab('tag:table').html
|
54
|
+
df = pd.read_html(io.StringIO(table_html))[0] # 读取表格
|
55
|
+
df.columns = [col.replace('\ue645', '') for col in df.columns]
|
56
|
+
# "星标"的内容特殊字符
|
57
|
+
df.replace('\ue66b', '', regex=True, inplace=True)
|
58
|
+
# "操作"的内容特殊字符
|
59
|
+
df.replace('\ue6a3\ue6d4', '', regex=True, inplace=True)
|
60
|
+
return df
|
61
|
+
|
62
|
+
def set_num_of_page(self, num_of_page):
|
63
|
+
""" 查看数据页面,设置每页显示多少条记录 """
|
64
|
+
select = self.tab('tag:span@@text():每页显示').next('tag:select')
|
65
|
+
select.click()
|
66
|
+
opt = select(f'tag:option@@text()={num_of_page}')
|
67
|
+
if opt.attr('selected') != 'selected':
|
68
|
+
opt.click()
|
69
|
+
else:
|
70
|
+
select.click()
|
71
|
+
|
72
|
+
def get_df(self, all_pages=False):
|
73
|
+
"""获得当前页面的表格数据,如果 all_pages 为 True,则下载所有页面的数据"""
|
74
|
+
# 初始化DataFrame列表,用于存储每页的数据
|
75
|
+
dfs = [self._parse_table()] # 获取当前页面的数据
|
76
|
+
|
77
|
+
# 如果需要下载所有页面数据
|
78
|
+
if all_pages:
|
79
|
+
current_idx, total_pages = self.get_page_num()
|
80
|
+
while current_idx < total_pages:
|
81
|
+
self.next_page() # 翻到下一页
|
82
|
+
time.sleep(2)
|
83
|
+
dfs.append(self._parse_table()) # 获取并处理新一页的数据
|
84
|
+
current_idx, total_pages = self.get_page_num() # 更新页码信息
|
85
|
+
|
86
|
+
# 将所有数据合并为一个DataFrame
|
87
|
+
final_df = pd.concat(dfs, ignore_index=True) if all_pages else dfs[0]
|
88
|
+
return final_df
|