pyxllib 0.3.197__py3-none-any.whl → 3.201.1__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 (127) hide show
  1. pyxllib/__init__.py +14 -21
  2. pyxllib/algo/__init__.py +8 -8
  3. pyxllib/algo/disjoint.py +54 -54
  4. pyxllib/algo/geo.py +537 -541
  5. pyxllib/algo/intervals.py +964 -964
  6. pyxllib/algo/matcher.py +389 -389
  7. pyxllib/algo/newbie.py +166 -166
  8. pyxllib/algo/pupil.py +629 -629
  9. pyxllib/algo/shapelylib.py +67 -67
  10. pyxllib/algo/specialist.py +241 -241
  11. pyxllib/algo/stat.py +494 -494
  12. pyxllib/algo/treelib.py +145 -149
  13. pyxllib/algo/unitlib.py +62 -66
  14. pyxllib/autogui/__init__.py +5 -5
  15. pyxllib/autogui/activewin.py +246 -246
  16. pyxllib/autogui/all.py +9 -9
  17. pyxllib/autogui/autogui.py +846 -852
  18. pyxllib/autogui/uiautolib.py +362 -362
  19. pyxllib/autogui/virtualkey.py +102 -102
  20. pyxllib/autogui/wechat.py +827 -827
  21. pyxllib/autogui/wechat_msg.py +421 -421
  22. pyxllib/autogui/wxautolib.py +84 -84
  23. pyxllib/cv/__init__.py +5 -5
  24. pyxllib/cv/expert.py +267 -267
  25. pyxllib/cv/imfile.py +159 -159
  26. pyxllib/cv/imhash.py +39 -39
  27. pyxllib/cv/pupil.py +9 -9
  28. pyxllib/cv/rgbfmt.py +1525 -1525
  29. pyxllib/cv/slidercaptcha.py +137 -137
  30. pyxllib/cv/trackbartools.py +251 -251
  31. pyxllib/cv/xlcvlib.py +1040 -1040
  32. pyxllib/cv/xlpillib.py +423 -423
  33. pyxllib/data/echarts.py +236 -240
  34. pyxllib/data/jsonlib.py +85 -89
  35. pyxllib/data/oss.py +72 -72
  36. pyxllib/data/pglib.py +1111 -1127
  37. pyxllib/data/sqlite.py +568 -568
  38. pyxllib/data/sqllib.py +297 -297
  39. pyxllib/ext/JLineViewer.py +505 -505
  40. pyxllib/ext/__init__.py +6 -6
  41. pyxllib/ext/demolib.py +251 -246
  42. pyxllib/ext/drissionlib.py +277 -277
  43. pyxllib/ext/kq5034lib.py +12 -12
  44. pyxllib/ext/qt.py +449 -449
  45. pyxllib/ext/robustprocfile.py +493 -497
  46. pyxllib/ext/seleniumlib.py +76 -76
  47. pyxllib/ext/tk.py +173 -173
  48. pyxllib/ext/unixlib.py +821 -827
  49. pyxllib/ext/utools.py +345 -351
  50. pyxllib/ext/webhook.py +124 -119
  51. pyxllib/ext/win32lib.py +40 -40
  52. pyxllib/ext/wjxlib.py +91 -88
  53. pyxllib/ext/wpsapi.py +124 -124
  54. pyxllib/ext/xlwork.py +9 -9
  55. pyxllib/ext/yuquelib.py +1110 -1105
  56. pyxllib/file/__init__.py +17 -17
  57. pyxllib/file/docxlib.py +757 -761
  58. pyxllib/file/gitlib.py +309 -309
  59. pyxllib/file/libreoffice.py +165 -165
  60. pyxllib/file/movielib.py +144 -148
  61. pyxllib/file/newbie.py +10 -10
  62. pyxllib/file/onenotelib.py +1469 -1469
  63. pyxllib/file/packlib/__init__.py +330 -330
  64. pyxllib/file/packlib/zipfile.py +2441 -2441
  65. pyxllib/file/pdflib.py +422 -426
  66. pyxllib/file/pupil.py +185 -185
  67. pyxllib/file/specialist/__init__.py +681 -685
  68. pyxllib/file/specialist/dirlib.py +799 -799
  69. pyxllib/file/specialist/download.py +193 -193
  70. pyxllib/file/specialist/filelib.py +2825 -2829
  71. pyxllib/file/xlsxlib.py +3122 -3131
  72. pyxllib/file/xlsyncfile.py +341 -341
  73. pyxllib/prog/__init__.py +5 -5
  74. pyxllib/prog/cachetools.py +58 -64
  75. pyxllib/prog/deprecatedlib.py +233 -233
  76. pyxllib/prog/filelock.py +42 -42
  77. pyxllib/prog/ipyexec.py +253 -253
  78. pyxllib/prog/multiprogs.py +940 -940
  79. pyxllib/prog/newbie.py +451 -451
  80. pyxllib/prog/pupil.py +1208 -1197
  81. pyxllib/prog/sitepackages.py +33 -33
  82. pyxllib/prog/specialist/__init__.py +348 -391
  83. pyxllib/prog/specialist/bc.py +203 -203
  84. pyxllib/prog/specialist/browser.py +497 -497
  85. pyxllib/prog/specialist/common.py +347 -347
  86. pyxllib/prog/specialist/datetime.py +198 -198
  87. pyxllib/prog/specialist/tictoc.py +240 -240
  88. pyxllib/prog/specialist/xllog.py +180 -180
  89. pyxllib/prog/xlosenv.py +110 -108
  90. pyxllib/stdlib/__init__.py +17 -17
  91. pyxllib/stdlib/tablepyxl/__init__.py +10 -10
  92. pyxllib/stdlib/tablepyxl/style.py +303 -303
  93. pyxllib/stdlib/tablepyxl/tablepyxl.py +130 -130
  94. pyxllib/text/__init__.py +8 -8
  95. pyxllib/text/ahocorasick.py +36 -39
  96. pyxllib/text/airscript.js +754 -744
  97. pyxllib/text/charclasslib.py +121 -121
  98. pyxllib/text/jiebalib.py +267 -267
  99. pyxllib/text/jinjalib.py +27 -32
  100. pyxllib/text/jsa_ai_prompt.md +271 -271
  101. pyxllib/text/jscode.py +922 -922
  102. pyxllib/text/latex/__init__.py +158 -158
  103. pyxllib/text/levenshtein.py +303 -303
  104. pyxllib/text/nestenv.py +1215 -1215
  105. pyxllib/text/newbie.py +300 -300
  106. pyxllib/text/pupil/__init__.py +8 -8
  107. pyxllib/text/pupil/common.py +1121 -1121
  108. pyxllib/text/pupil/xlalign.py +326 -326
  109. pyxllib/text/pycode.py +47 -47
  110. pyxllib/text/specialist/__init__.py +8 -8
  111. pyxllib/text/specialist/common.py +112 -112
  112. pyxllib/text/specialist/ptag.py +186 -186
  113. pyxllib/text/spellchecker.py +172 -172
  114. pyxllib/text/templates/echart_base.html +10 -10
  115. pyxllib/text/templates/highlight_code.html +16 -16
  116. pyxllib/text/templates/latex_editor.html +102 -102
  117. pyxllib/text/vbacode.py +17 -17
  118. pyxllib/text/xmllib.py +741 -747
  119. pyxllib/xl.py +42 -39
  120. pyxllib/xlcv.py +17 -17
  121. pyxllib-3.201.1.dist-info/METADATA +296 -0
  122. pyxllib-3.201.1.dist-info/RECORD +125 -0
  123. {pyxllib-0.3.197.dist-info → pyxllib-3.201.1.dist-info}/licenses/LICENSE +190 -190
  124. pyxllib/ext/old.py +0 -663
  125. pyxllib-0.3.197.dist-info/METADATA +0 -48
  126. pyxllib-0.3.197.dist-info/RECORD +0 -126
  127. {pyxllib-0.3.197.dist-info → pyxllib-3.201.1.dist-info}/WHEEL +0 -0
pyxllib/ext/utools.py CHANGED
@@ -1,351 +1,345 @@
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
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
+
10
+ import datetime
11
+ import json
12
+ import os
13
+ import pathlib
14
+ import pyperclip
15
+ import re
16
+
17
+ import fire
18
+ from humanfriendly import format_timespan
19
+ import pandas as pd
20
+ import pyautogui
21
+
22
+ from pyxllib.prog.specialist import browser, TicToc, parse_datetime
23
+ from pyxllib.file.specialist import XlPath
24
+ from pyxllib.autogui.autogui import type_text, clipboard_decorator
25
+
26
+
27
+ def _print_df_result(df, outfmt='text'):
28
+ # subinput可以强制重置输出类型
29
+ # TODO argparser可以对str类型进行参数解析?
30
+ # if 'browser' in kwargs['subinput']:
31
+ # outfmt = 'browser'
32
+ # elif 'html' in kwargs['subinput']:
33
+ # outfmt = 'html'
34
+ # elif 'text' in kwargs['subinput']:
35
+ # outfmt = 'text'
36
+
37
+ if outfmt == 'html':
38
+ # content = df.to_html().replace('', '-') # 很多app会出现一个utf8特殊的-,转gbk编码会出错+
39
+ try: # utools里按gbk会有很多编码问题,如果不能直接显示,就打开浏览器看
40
+ print(df.to_html())
41
+ except UnicodeEncodeError:
42
+ browser(df)
43
+ elif outfmt == 'browser':
44
+ browser(df)
45
+ else:
46
+ with pd.option_context('display.max_colwidth', -1, 'display.max_columns', 20,
47
+ 'display.width', 200): # 上下文控制格式
48
+ print(df)
49
+
50
+
51
+ class UtoolsBase:
52
+ def __init__(self, cmds, *, outfmt='text'):
53
+ """
54
+ :param cmds: "快捷命令"响应的命令集,即调用出可传入的输入文件、所在窗口、选中文件等信息
55
+ :param outfmt: 输出格式
56
+ text,纯文本
57
+ html,html格式
58
+ browser,用浏览器打开
59
+ """
60
+ # 1 删除没有值的键
61
+ self.cmds = dict()
62
+ for k, v in cmds.items():
63
+ if cmds[k] not in ('{{' + k + '}}', ''):
64
+ self.cmds[k] = v
65
+
66
+ # 2 「快捷命令」的ClipText宏展开,如果存在连续3个单引号,会跟py字符串定义冲突,产生bug
67
+ # 所以这段不建议用宏展开,这里该类会自动调用pyperclip获取
68
+ # 这个可以作为可选功能关闭,但实验证明这个对性能其实没有太大影响
69
+ if 'ClipText' not in self.cmds:
70
+ self.cmds['ClipText'] = pyperclip.paste().replace('\r\n', '\n')
71
+
72
+ # 3 解析json数据
73
+ # 解析window模式的WindowInfo
74
+ if 'WindowInfo' in self.cmds:
75
+ self.cmds['WindowInfo'] = json.loads(self.cmds['WindowInfo'].encode('utf8'))
76
+
77
+ # 解析files模式的MatchedFiles
78
+ if 'MatchedFiles' in self.cmds:
79
+ # 右边引用没写错。因为MatchedFiles本身获取内容不稳定,可能会有bug,取更稳定的payload来初始化MatchedFiles
80
+ self.cmds['MatchedFiles'] = json.loads(self.cmds['payload'].encode('utf8'))
81
+ # 在有些软件中,比如everything,是可以选中多个不同目录的文件的。。。所以严格来说不能直接按第一个文件算pwd
82
+ # 但这个讯息是可以写一个工具分析的
83
+ # self.cmds['pwd'] = os.path.dirname(self.cmds['MatchedFiles'][0]['path'])
84
+
85
+ # TODO 如果选中的文件全部是同一个目录下的,要存储pwd
86
+ dirs = {os.path.dirname(f['path']) for f in self.cmds['MatchedFiles']}
87
+ if len(dirs) == 1:
88
+ self.cmds['pwd'] = list(dirs)[0]
89
+
90
+ # 4 当前工作目录
91
+ # 要根据场景切换pwd
92
+ # files默认工作目录是app: C:\Users\chen\AppData\Local\Programs\utools
93
+ # window默认工作目录是exe
94
+ # window模式存储的pwd跟各种软件有关
95
+ # explorer就是当前窗口,没问题
96
+ # onenote存的是桌面
97
+ if 'pwd' in self.cmds:
98
+ os.chdir(self.cmds['pwd'])
99
+
100
+ # 5 subinput可以用字典结构扩展参数
101
+ if 'subinput' in self.cmds:
102
+ if re.match(r'\{.+\}$', self.cmds['subinput']):
103
+ ext_kwargs = eval(self.cmds['subinput'])
104
+ self.cmds.update(ext_kwargs)
105
+ else:
106
+ self.cmds['subinput'] = ''
107
+
108
+ self.outfmt = self.cmds['outfmt'] if 'outfmt' in self.cmds else outfmt
109
+
110
+
111
+ class UtoolsName(UtoolsBase):
112
+
113
+ def check_cmds(self):
114
+ """ 显示所有参数值 """
115
+ df = pd.DataFrame.from_records([(k, v) for k, v in self.cmds.items()],
116
+ columns=['key', 'content'])
117
+ _print_df_result(df, self.outfmt)
118
+
119
+ def bcompare(self):
120
+ """ 可以通过subinput设置文件后缀类型 """
121
+ from pyxllib.prog.specialist import bcompare
122
+
123
+ suffix = self.cmds.get('subinput', None)
124
+ try:
125
+ f1 = XlPath.init('left', XlPath.tempdir(), suffix=suffix)
126
+ f2 = XlPath.init('right', XlPath.tempdir(), suffix=suffix)
127
+ except OSError: # 忽略错误的扩展名
128
+ f1 = XlPath.init('left', XlPath.tempdir())
129
+ f2 = XlPath.init('right', XlPath.tempdir())
130
+ f1.write_text(self.cmds['ClipText'])
131
+ f2.write_text(self.cmds['ClipText'] + '\n')
132
+ bcompare(f1, f2, wait=False)
133
+
134
+ @clipboard_decorator(copy=False, typing=True)
135
+ def common_dir(self):
136
+ # 目录路径生成工具
137
+ from pyxlpr.data.datasets import common_path
138
+
139
+ def func(name, unix_path=False):
140
+ p = getattr(common_path, name)
141
+
142
+ if unix_path:
143
+ # 因为用了符号链接,实际位置会变回D:/slns,这里需要反向替换下
144
+ p = str(p).replace('D:/slns', 'D:/home/chenkunze/slns')
145
+ p = str(p)[2:]
146
+ else:
147
+ # slns比较特别,本地要重定向到D:/slns
148
+ p = str(p).replace('D:/home/chenkunze/slns', 'D:/slns')
149
+ p = p.replace('/', '\\')
150
+
151
+ return p
152
+
153
+ return fire.Fire(func, self.cmds['subinput'], 'CommonDir')
154
+
155
+ def wdate(self):
156
+ """ week dates 输入一周的简略日期值 """
157
+
158
+ def func(start):
159
+ weektag = '一二三四五六日'
160
+ for i in range(7):
161
+ dt = parse_datetime(start) + datetime.timedelta(i)
162
+ type_text(dt.strftime('%y%m%d') + f'周{weektag[dt.weekday()]}')
163
+ pyautogui.press('down')
164
+
165
+ fire.Fire(func, self.cmds['subinput'], 'wdate')
166
+
167
+ def input_digits(self):
168
+ """ 输入数字 """
169
+
170
+ def func(start, stop, step=1):
171
+ for i in range(start, stop, step):
172
+ pyautogui.write(str(i))
173
+ pyautogui.press('down')
174
+
175
+ fire.Fire(func, self.cmds['subinput'], 'input_digits')
176
+
177
+ def __win32(self):
178
+ """ win32相关自动化
179
+
180
+ 目前主要是word自动化相关的功能,这里有很多demo可以学习怎么用win32做word的自动化
181
+ """
182
+ pass
183
+
184
+ def browser(self):
185
+ """ 将内容复制到word,另存为html文件后,用浏览器打开查看 """
186
+ from pyxllib.file.docxlib import rebuild_document_by_word
187
+
188
+ file = fire.Fire(rebuild_document_by_word, self.cmds['subinput'], 'browser')
189
+ browser(file)
190
+
191
+ def create_text_image(self):
192
+ from pyxllib.xl import XlPath
193
+ from pyxllib.xlcv import xlpil, create_text_image
194
+
195
+ @clipboard_decorator('html')
196
+ def func(text, font_size=20, *, size=None, xy=None, bg_color=None, text_color=None):
197
+ im = create_text_image(text, size, xy=xy, font_size=font_size, bg_color=bg_color, text_color=text_color)
198
+ # 这种只能语雀里用
199
+ data = xlpil.to_buffer(im, b64encode=True).decode()
200
+ res = '<img src="data:image/jpg;base64,' + data + '"/>'
201
+ # 这种只能onenote、qq、微信里用
202
+ p = XlPath.tempdir() / 'create_text_image.jpg'
203
+ im.save(p)
204
+ res += f'<img src="{p}"/>'
205
+ # 那么我把两种都复制,就能兼容多种场景了~~
206
+ return res
207
+
208
+ fire.Fire(func, self.cmds['subinput'], 'create_text_image')
209
+
210
+
211
+ class UtoolsText(UtoolsBase):
212
+ def __init__(self, cmds, *, outfmt='text'):
213
+ super().__init__(cmds, outfmt=outfmt)
214
+
215
+ @clipboard_decorator(paste=True)
216
+ def coderegex(self):
217
+ # tt = TicToc()
218
+ s = self.cmds['ClipText']
219
+ return eval(self.cmds['subinput'])
220
+ # print(f'finished in {format_timespan(tt.tocvalue())}.')
221
+
222
+ def refine_text(self, func, rtype='text', *, copy=True, paste=True, typing=False):
223
+ from functools import partial
224
+
225
+ @clipboard_decorator(rtype=rtype, copy=copy, paste=paste, typing=typing)
226
+ def _refine():
227
+ return fire.Fire(partial(func, self.cmds['ClipText']),
228
+ self.cmds['subinput'], func.__name__)
229
+
230
+ return _refine()
231
+
232
+ def bc_text(self, func):
233
+ """ 用bc软件打开,对比前后文本变化,有用户自己决定如何处理保留 """
234
+ from functools import partial
235
+ from pyxllib.prog.specialist import bcompare
236
+
237
+ text1 = self.cmds['ClipText']
238
+ text2 = fire.Fire(partial(func, text1), self.cmds['subinput'], func.__name__)
239
+ bcompare(text1, text2)
240
+
241
+
242
+ class UtoolsFile(UtoolsBase):
243
+ """ 文件相关操作工具 """
244
+
245
+ def __init__(self, cmds, *, outfmt='text'):
246
+ super().__init__(cmds, outfmt=outfmt)
247
+
248
+ # 如果是window等模式,补充 MatchedFiles
249
+ if 'MatchedFiles' in self.cmds:
250
+ self.paths = [pathlib.Path(f['path']) for f in self.cmds['MatchedFiles']]
251
+ else:
252
+ self.paths = [pathlib.Path(os.path.abspath(f)).resolve() for f in os.listdir('../robot')]
253
+
254
+ @classmethod
255
+ def is_image(cls, f):
256
+ return bool(re.match('jpe?g|png|gif|bmp', f.suffix.lower()[1:]))
257
+
258
+ def codefile(self):
259
+ """ 多用途文件处理器
260
+
261
+ 核心理念是不设定具体功能,而是让用户在 subinput 自己写需要执行的py代码功能
262
+ 而在subinput,可以用一些特殊的标识符来传参
263
+
264
+ 以file为例,有4类参数:
265
+ file,处理当前目录 文件
266
+ rfile,递归处理所有目录下 文件
267
+ files,当前目录 所有文件
268
+ rfiles,递归获取所有目录下 所有文件
269
+
270
+ 类型上,file可以改为
271
+ dir,只分析目录
272
+ path,所有文件和目录
273
+
274
+ 扩展了一些特殊的类型:
275
+ imfile,图片文件
276
+ """
277
+ from functools import reduce
278
+ try:
279
+ from xlproject.xyz.kzconfig import KzDataSync
280
+ from pyxlpr.data.labelme import reduce_labelme_jsonfile
281
+ except ModuleNotFoundError:
282
+ pass
283
+
284
+ tt = TicToc()
285
+
286
+ # 1 获得所有标识符
287
+ # 每个单词前后都有空格,方便定界
288
+ keywords = filter(lambda x: re.match(r'[a-zA-Z_]+$', x),
289
+ set(re.findall(r'\.?[a-zA-Z_]+\(?', self.cmds['subinput'])))
290
+ keywords = ' ' + ' '.join(keywords) + ' '
291
+ # print('keywords:', keywords)
292
+
293
+ # 2 生成一些备用的智能参数
294
+ # 一级目录下选中的文件
295
+ paths = self.paths
296
+ # 用正则判断一些智能参数是否要计算,注意不能只判断files,如果出现file,也是要引用files的
297
+ files = [XlPath(p) for p in paths if p.is_file()] if re.search(r'files? ', keywords) else []
298
+ imfiles = [f for f in files if self.is_image(f)] if re.search(r' imfiles? ', keywords) else []
299
+ # 出现r系列的递归操作,都是要计算出dirs的
300
+ dirs = [XlPath(p) for p in paths if p.is_dir()] if re.search(r' (dirs?|r[a-zA-Z_]+) ', keywords) else []
301
+
302
+ # 递归所有的文件
303
+ rpaths = reduce(lambda x, y: x + y.select('**/*').subpaths(), [paths] + dirs) \
304
+ if re.search(r' (r[a-zA-Z_]+) ', keywords) else []
305
+ rfiles = [XlPath(p) for p in rpaths if p.is_file()] if re.search(r' (r(im)?files?) ', keywords) else []
306
+ rimfiles = [f for f in rfiles if self.is_image(f)] if re.search(r' rimfiles? ', keywords) else []
307
+ rdirs = [XlPath(p) for p in rpaths if p.is_dir()] if re.search(r' (rdirs?) ', keywords) else []
308
+
309
+ # 3 判断是否要智能开循环处理
310
+ m = re.search(r' (r?(path|(im)?file|dir)) ', keywords)
311
+ if m:
312
+ name = m.group(1)
313
+ objs = eval(name + 's')
314
+ print('len(' + name + 's)=', len(objs))
315
+ for x in objs:
316
+ locals()[name] = x
317
+ eval(self.cmds['subinput'])
318
+ else: # 没有的话就直接处理所有文件
319
+ eval(self.cmds['subinput'])
320
+
321
+ # 4 运行结束标志
322
+ print(f'finished in {format_timespan(tt.tocvalue())}.')
323
+
324
+ def open_jsonl(self):
325
+ """ 打开jsonl文件 """
326
+ from pyxllib.ext.JLineViewer import start_jlineviewer
327
+ start_jlineviewer(self.paths[0].as_posix())
328
+
329
+ def open_jsonl_with_excel(self):
330
+ """ 用excel打开jsonl文件 """
331
+ from pyxllib.algo.stat import write_dataframes_to_excel
332
+
333
+ file = self.paths[0].as_posix()
334
+ data = XlPath(file).read_jsonl()
335
+
336
+ file = XlPath.create_tempfile_path('.xlsx')
337
+ df = pd.DataFrame.from_dict(data)
338
+ write_dataframes_to_excel(file, {'Sheet1': df})
339
+
340
+ os.startfile(file)
341
+
342
+
343
+ if __name__ == '__main__':
344
+ with TicToc('utools'):
345
+ pass