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.
Files changed (186) hide show
  1. pyxllib/__init__.py +9 -2
  2. pyxllib/algo/__init__.py +8 -0
  3. pyxllib/algo/disjoint.py +54 -0
  4. pyxllib/algo/geo.py +541 -0
  5. pyxllib/{util/mathlib.py → algo/intervals.py} +172 -36
  6. pyxllib/algo/matcher.py +389 -0
  7. pyxllib/algo/newbie.py +166 -0
  8. pyxllib/algo/pupil.py +629 -0
  9. pyxllib/algo/shapelylib.py +67 -0
  10. pyxllib/algo/specialist.py +241 -0
  11. pyxllib/algo/stat.py +494 -0
  12. pyxllib/algo/treelib.py +149 -0
  13. pyxllib/algo/unitlib.py +66 -0
  14. pyxllib/autogui/__init__.py +5 -0
  15. pyxllib/autogui/activewin.py +246 -0
  16. pyxllib/autogui/all.py +9 -0
  17. pyxllib/autogui/autogui.py +852 -0
  18. pyxllib/autogui/uiautolib.py +362 -0
  19. pyxllib/autogui/virtualkey.py +102 -0
  20. pyxllib/autogui/wechat.py +827 -0
  21. pyxllib/autogui/wechat_msg.py +421 -0
  22. pyxllib/autogui/wxautolib.py +84 -0
  23. pyxllib/cv/__init__.py +1 -11
  24. pyxllib/cv/expert.py +267 -0
  25. pyxllib/cv/{imlib.py → imfile.py} +18 -83
  26. pyxllib/cv/imhash.py +39 -0
  27. pyxllib/cv/pupil.py +9 -0
  28. pyxllib/cv/rgbfmt.py +1525 -0
  29. pyxllib/cv/slidercaptcha.py +137 -0
  30. pyxllib/cv/trackbartools.py +163 -49
  31. pyxllib/cv/xlcvlib.py +1040 -0
  32. pyxllib/cv/xlpillib.py +423 -0
  33. pyxllib/data/__init__.py +0 -0
  34. pyxllib/data/echarts.py +240 -0
  35. pyxllib/data/jsonlib.py +89 -0
  36. pyxllib/{util/oss2_.py → data/oss.py} +11 -9
  37. pyxllib/data/pglib.py +1127 -0
  38. pyxllib/data/sqlite.py +568 -0
  39. pyxllib/{util → data}/sqllib.py +13 -31
  40. pyxllib/ext/JLineViewer.py +505 -0
  41. pyxllib/ext/__init__.py +6 -0
  42. pyxllib/{util → ext}/demolib.py +119 -35
  43. pyxllib/ext/drissionlib.py +277 -0
  44. pyxllib/ext/kq5034lib.py +12 -0
  45. pyxllib/{util/main.py → ext/old.py} +122 -284
  46. pyxllib/ext/qt.py +449 -0
  47. pyxllib/ext/robustprocfile.py +497 -0
  48. pyxllib/ext/seleniumlib.py +76 -0
  49. pyxllib/{util/tklib.py → ext/tk.py} +10 -11
  50. pyxllib/ext/unixlib.py +827 -0
  51. pyxllib/ext/utools.py +351 -0
  52. pyxllib/{util/webhooklib.py → ext/webhook.py} +45 -17
  53. pyxllib/ext/win32lib.py +40 -0
  54. pyxllib/ext/wjxlib.py +88 -0
  55. pyxllib/ext/wpsapi.py +124 -0
  56. pyxllib/ext/xlwork.py +9 -0
  57. pyxllib/ext/yuquelib.py +1105 -0
  58. pyxllib/file/__init__.py +17 -0
  59. pyxllib/file/docxlib.py +761 -0
  60. pyxllib/{util → file}/gitlib.py +40 -27
  61. pyxllib/file/libreoffice.py +165 -0
  62. pyxllib/file/movielib.py +148 -0
  63. pyxllib/file/newbie.py +10 -0
  64. pyxllib/file/onenotelib.py +1469 -0
  65. pyxllib/file/packlib/__init__.py +330 -0
  66. pyxllib/{util → file/packlib}/zipfile.py +598 -195
  67. pyxllib/file/pdflib.py +426 -0
  68. pyxllib/file/pupil.py +185 -0
  69. pyxllib/file/specialist/__init__.py +685 -0
  70. pyxllib/{basic/_5_dirlib.py → file/specialist/dirlib.py} +364 -93
  71. pyxllib/file/specialist/download.py +193 -0
  72. pyxllib/file/specialist/filelib.py +2829 -0
  73. pyxllib/file/xlsxlib.py +3131 -0
  74. pyxllib/file/xlsyncfile.py +341 -0
  75. pyxllib/prog/__init__.py +5 -0
  76. pyxllib/prog/cachetools.py +64 -0
  77. pyxllib/prog/deprecatedlib.py +233 -0
  78. pyxllib/prog/filelock.py +42 -0
  79. pyxllib/prog/ipyexec.py +253 -0
  80. pyxllib/prog/multiprogs.py +940 -0
  81. pyxllib/prog/newbie.py +451 -0
  82. pyxllib/prog/pupil.py +1197 -0
  83. pyxllib/{sitepackages.py → prog/sitepackages.py} +5 -3
  84. pyxllib/prog/specialist/__init__.py +391 -0
  85. pyxllib/prog/specialist/bc.py +203 -0
  86. pyxllib/prog/specialist/browser.py +497 -0
  87. pyxllib/prog/specialist/common.py +347 -0
  88. pyxllib/prog/specialist/datetime.py +199 -0
  89. pyxllib/prog/specialist/tictoc.py +240 -0
  90. pyxllib/prog/specialist/xllog.py +180 -0
  91. pyxllib/prog/xlosenv.py +108 -0
  92. pyxllib/stdlib/__init__.py +17 -0
  93. pyxllib/{util → stdlib}/tablepyxl/__init__.py +1 -3
  94. pyxllib/{util → stdlib}/tablepyxl/style.py +1 -1
  95. pyxllib/{util → stdlib}/tablepyxl/tablepyxl.py +2 -4
  96. pyxllib/text/__init__.py +8 -0
  97. pyxllib/text/ahocorasick.py +39 -0
  98. pyxllib/text/airscript.js +744 -0
  99. pyxllib/text/charclasslib.py +121 -0
  100. pyxllib/text/jiebalib.py +267 -0
  101. pyxllib/text/jinjalib.py +32 -0
  102. pyxllib/text/jsa_ai_prompt.md +271 -0
  103. pyxllib/text/jscode.py +922 -0
  104. pyxllib/text/latex/__init__.py +158 -0
  105. pyxllib/text/levenshtein.py +303 -0
  106. pyxllib/text/nestenv.py +1215 -0
  107. pyxllib/text/newbie.py +300 -0
  108. pyxllib/text/pupil/__init__.py +8 -0
  109. pyxllib/text/pupil/common.py +1121 -0
  110. pyxllib/text/pupil/xlalign.py +326 -0
  111. pyxllib/text/pycode.py +47 -0
  112. pyxllib/text/specialist/__init__.py +8 -0
  113. pyxllib/text/specialist/common.py +112 -0
  114. pyxllib/text/specialist/ptag.py +186 -0
  115. pyxllib/text/spellchecker.py +172 -0
  116. pyxllib/text/templates/echart_base.html +11 -0
  117. pyxllib/text/templates/highlight_code.html +17 -0
  118. pyxllib/text/templates/latex_editor.html +103 -0
  119. pyxllib/text/vbacode.py +17 -0
  120. pyxllib/text/xmllib.py +747 -0
  121. pyxllib/xl.py +39 -0
  122. pyxllib/xlcv.py +17 -0
  123. pyxllib-0.3.197.dist-info/METADATA +48 -0
  124. pyxllib-0.3.197.dist-info/RECORD +126 -0
  125. {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info}/WHEEL +4 -5
  126. pyxllib/basic/_1_strlib.py +0 -945
  127. pyxllib/basic/_2_timelib.py +0 -488
  128. pyxllib/basic/_3_pathlib.py +0 -916
  129. pyxllib/basic/_4_loglib.py +0 -419
  130. pyxllib/basic/__init__.py +0 -54
  131. pyxllib/basic/arrow_.py +0 -250
  132. pyxllib/basic/chardet_.py +0 -66
  133. pyxllib/basic/dirlib.py +0 -529
  134. pyxllib/basic/dprint.py +0 -202
  135. pyxllib/basic/extension.py +0 -12
  136. pyxllib/basic/judge.py +0 -31
  137. pyxllib/basic/log.py +0 -204
  138. pyxllib/basic/pathlib_.py +0 -705
  139. pyxllib/basic/pytictoc.py +0 -102
  140. pyxllib/basic/qiniu_.py +0 -61
  141. pyxllib/basic/strlib.py +0 -761
  142. pyxllib/basic/timer.py +0 -132
  143. pyxllib/cv/cv.py +0 -834
  144. pyxllib/cv/cvlib/_1_geo.py +0 -543
  145. pyxllib/cv/cvlib/_2_cvprcs.py +0 -309
  146. pyxllib/cv/cvlib/_2_imgproc.py +0 -594
  147. pyxllib/cv/cvlib/_3_pilprcs.py +0 -80
  148. pyxllib/cv/cvlib/_4_cvimg.py +0 -211
  149. pyxllib/cv/cvlib/__init__.py +0 -10
  150. pyxllib/cv/debugtools.py +0 -82
  151. pyxllib/cv/fitz_.py +0 -300
  152. pyxllib/cv/installer.py +0 -42
  153. pyxllib/debug/_0_installer.py +0 -38
  154. pyxllib/debug/_1_typelib.py +0 -277
  155. pyxllib/debug/_2_chrome.py +0 -198
  156. pyxllib/debug/_3_showdir.py +0 -161
  157. pyxllib/debug/_4_bcompare.py +0 -140
  158. pyxllib/debug/__init__.py +0 -49
  159. pyxllib/debug/bcompare.py +0 -132
  160. pyxllib/debug/chrome.py +0 -198
  161. pyxllib/debug/installer.py +0 -38
  162. pyxllib/debug/showdir.py +0 -158
  163. pyxllib/debug/typelib.py +0 -278
  164. pyxllib/image/__init__.py +0 -12
  165. pyxllib/torch/__init__.py +0 -20
  166. pyxllib/torch/modellib.py +0 -37
  167. pyxllib/torch/trainlib.py +0 -344
  168. pyxllib/util/__init__.py +0 -20
  169. pyxllib/util/aip_.py +0 -141
  170. pyxllib/util/casiadb.py +0 -59
  171. pyxllib/util/excellib.py +0 -495
  172. pyxllib/util/filelib.py +0 -612
  173. pyxllib/util/jsondata.py +0 -27
  174. pyxllib/util/jsondata2.py +0 -92
  175. pyxllib/util/labelmelib.py +0 -139
  176. pyxllib/util/onepy/__init__.py +0 -29
  177. pyxllib/util/onepy/onepy.py +0 -574
  178. pyxllib/util/onepy/onmanager.py +0 -170
  179. pyxllib/util/pyautogui_.py +0 -219
  180. pyxllib/util/textlib.py +0 -1305
  181. pyxllib/util/unorder.py +0 -22
  182. pyxllib/util/xmllib.py +0 -639
  183. pyxllib-0.0.43.dist-info/METADATA +0 -39
  184. pyxllib-0.0.43.dist-info/RECORD +0 -80
  185. pyxllib-0.0.43.dist-info/top_level.txt +0 -1
  186. {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
- # @Data : 2020/10/21 09:22
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, url, secret):
40
- self.url = url
41
- self.url += self.add_secret(secret)
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'&timestamp={timestamp}&sign={sign}'
55
56
 
56
- def push_data(self, data):
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
- pass
60
+ except requests.exceptions.ConnectionError as e: # 没网发送失败的时候也不报错
61
+ raise e
61
62
 
62
- def push_text(self, content):
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.push_data(data)
68
+ self.send_data(data)
68
69
 
69
- def push_link(self, text='', title='', pic_url='', message_url=''):
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.push_data(data)
78
+ self.send_data(data)
78
79
 
79
- def push_markdown(self, text='', title=''):
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.push_data(data)
86
+ self.send_data(data)
86
87
 
87
- def push_actioncard(self, text='', title='', siggle_url='', siggle_title='', btn_orientation='0'):
88
+ def send_actioncard(self, text='', title='', siggle_url='', siggle_title='', btn_orientation='0'):
88
89
  raise NotImplementedError
89
90
 
90
- def push_feedcard(self):
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)}')
@@ -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