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
@@ -2,20 +2,22 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  # @Author : 陈坤泽
4
4
  # @Email : 877362867@qq.com
5
- # @Data : 2020/05/30 09:36
5
+ # @Date : 2020/05/30 09:36
6
6
 
7
7
 
8
8
  r"""
9
+ 一个旧模块,用来把pyxllib自动配置到 site-package 的工具
10
+ 使用 python setup.py develop 策略后就不太需要了
11
+
9
12
  除了使用pip install pyxllib,
10
13
  还可以下载最新源代码,然后用该脚本将pyxllib配置到site-packages里,便于索引使用
11
14
  """
12
15
 
13
-
14
16
  import os
15
17
  import site
16
18
 
17
19
 
18
- def add_project_to_sitepackages(project_path = os.path.dirname(__file__)):
20
+ def add_project_to_sitepackages(project_path=os.path.dirname(__file__)):
19
21
  """ 将制定项目配置到sitepackages目录
20
22
  :param project_path: 项目路径
21
23
  默认本脚本所在的目录为项目路径
@@ -0,0 +1,391 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # @Author : 陈坤泽
4
+ # @Email : 877362867@qq.com
5
+ # @Date : 2021/06/06 18:40
6
+
7
+
8
+ from pyxllib.prog.specialist.common import *
9
+ from pyxllib.prog.specialist.xllog import *
10
+ from pyxllib.prog.specialist.browser import *
11
+ from pyxllib.prog.specialist.bc import *
12
+ from pyxllib.prog.specialist.tictoc import *
13
+ from pyxllib.prog.specialist.datetime import *
14
+
15
+ import concurrent.futures
16
+ import os
17
+ import re
18
+ import subprocess
19
+ import time
20
+ from statistics import mean
21
+ from threading import Thread
22
+
23
+ from tqdm import tqdm
24
+ import requests
25
+ from humanfriendly import parse_size
26
+
27
+ from pyxllib.prog.newbie import human_readable_size
28
+ from pyxllib.prog.pupil import get_installed_packages, aligned_range, percentage_and_value
29
+ from pyxllib.prog.xlosenv import XlOsEnv
30
+ from pyxllib.file.specialist import cache_file
31
+
32
+
33
+ def mtqdm(func, iterable, *args, max_workers=1, check_per_seconds=0.01, **kwargs):
34
+ """ 对tqdm的封装,增加了多线程的支持
35
+
36
+ 这里名称前缀多出的m有multi的意思
37
+
38
+ :param max_workers: 默认是单线程,改成None会自动变为多线程
39
+ 或者可以自己指定线程数
40
+ 注意,使用负数,可以用对等绝对值数据的“多进程”
41
+ :param smoothing: tqdm官方默认值是0.3
42
+ 这里关掉指数移动平均,直接计算整体平均速度
43
+ 因为对我个人来说,大部分时候需要严谨地分析性能,得到整体平均速度,而不是预估当前速度
44
+ :param mininterval: 官方默认值是0.1,表示显示更新间隔秒数
45
+ 这里不用那么频繁,每秒更新就行了~~
46
+ :param check_per_seconds: 每隔多少秒检查队列
47
+ 有些任务,这个值故意设大一点,可以减少频繁的队列检查时间,提高运行速度
48
+ 整体功能类似Iterate
49
+ """
50
+
51
+ # 0 个人习惯参数
52
+ kwargs['smoothing'] = kwargs.get('smoothing', 0)
53
+ kwargs['mininterval'] = kwargs.get('mininterval', 1)
54
+
55
+ if max_workers == 1:
56
+ # 1 如果只用一个线程,则不使用concurrent.futures.ThreadPoolExecutor,能加速
57
+ for x in tqdm(iterable, *args, **kwargs):
58
+ func(x)
59
+ else:
60
+ # 2 默认的多线程运行机制,出错是不会暂停的;这里对原函数功能进行封装,增加报错功能
61
+ error = False
62
+
63
+ def wrap_func(x):
64
+ nonlocal error
65
+ try:
66
+ func(x)
67
+ except Exception as e:
68
+ error = e
69
+
70
+ # 3 多线程/多进程 和 进度条 功能的结合
71
+ if max_workers > 1:
72
+ executor = concurrent.futures.ThreadPoolExecutor(max_workers)
73
+ for x in tqdm(iterable, *args, **kwargs):
74
+ while executor._work_queue.qsize():
75
+ if check_per_seconds:
76
+ time.sleep(check_per_seconds)
77
+ executor.submit(wrap_func, x)
78
+ if error:
79
+ raise error
80
+ else:
81
+ executor = concurrent.futures.ProcessPoolExecutor(-max_workers)
82
+ for x in tqdm(iterable, *args, **kwargs):
83
+ # while executor._call_queue.pending_work_items:
84
+ # if check_per_seconds:
85
+ # time.sleep(check_per_seconds)
86
+ executor.submit(wrap_func, x)
87
+ if error:
88
+ raise error
89
+
90
+ executor.shutdown()
91
+
92
+
93
+ def distribute_package(root, version=None, repository=None, *,
94
+ upload=True,
95
+ version_file='setup.py',
96
+ delete_dist=True):
97
+ """ 发布包的工具函数
98
+
99
+ :param root: 项目的根目录,例如 'D:/slns/pyxllib'
100
+ 根目录下有对应的 setup.py 等文件
101
+ :param repository: 比如我配置了 [xlpr],就可以传入 'xlpr'
102
+ :param version_file: 保存版本号的文件,注意看正则规则,需要满足特定的范式,才会自动更新版本号
103
+ :param delete_dist: 上传完是否自动删除dist目录,要检查上传包是否有遗漏时,要关闭
104
+ """
105
+ import sys
106
+ from pyxllib.file.specialist import XlPath
107
+
108
+ # 1 切换工作目录
109
+ os.chdir(str(root))
110
+
111
+ # 2 改版本号
112
+ if version:
113
+ f = XlPath(root) / version_file
114
+ s = re.sub(r"(version\s*=\s*)(['\"])(.+?)(\2)", fr'\1\g<2>{version}\4', f.read_text())
115
+ f.write_text(s)
116
+
117
+ # 3 打包
118
+ subprocess.run(f'{sys.executable} setup.py sdist')
119
+
120
+ # 4 上传
121
+ if upload:
122
+ # 上传
123
+ cmd = 'twine upload dist/*'
124
+ if repository:
125
+ cmd += f' -r {repository}'
126
+ subprocess.run(cmd)
127
+ # 删除打包生成的中间文件
128
+ if delete_dist:
129
+ XlPath('dist').delete()
130
+ XlPath('build').delete()
131
+
132
+ # 这个不能删,不然importlib会读取不到模块的版本号
133
+ # [d.delete() for d in XlPath('.').select_dirs(r'*.egg-info')]
134
+
135
+
136
+ def estimate_package_size(package):
137
+ """ 估计一个库占用的存储大小 """
138
+
139
+ # 将cache文件存储到临时目录中,避免重复获取网页
140
+ def get_size(package):
141
+ r = requests.get(f'https://pypi.org/project/{package}/#files')
142
+ if r.status_code == 404:
143
+ return '(0 MB' # 找不到的包默认按0MB计算
144
+ else:
145
+ return r.text
146
+
147
+ s = cache_file(package + '.pypi', lambda: get_size(package))
148
+ # 找出所有包大小,计算平均值作为这个包大小的预估
149
+ # 注意,这里进位是x1000,不是x1024
150
+ v = mean(list(map(parse_size, re.findall(r'\((\d+(?:\.\d+)?\s*\wB(?:ytes)?)', s))) or [0])
151
+ return v
152
+
153
+
154
+ def estimate_pip_packages(*, print_mode=False):
155
+ """ 检查pip list中包的大小,从大到小排序
156
+
157
+ :param print_mode:
158
+ 0,不输出,只返回运算结果,[(package_name, package_size), ...]
159
+ 1,输出最后的美化过的运算表格
160
+ 2,输出中间计算过程
161
+ """
162
+
163
+ def printf(*args, **kwargs):
164
+ # dm表示mode增量
165
+ if print_mode > 1:
166
+ print(*args, **kwargs)
167
+
168
+ packages = get_installed_packages()
169
+ package_sizes = []
170
+ for package_name in packages:
171
+ package_size = estimate_package_size(package_name)
172
+ package_sizes.append((package_name, package_size))
173
+ printf(f"{package_name}: {human_readable_size(package_size)}")
174
+
175
+ package_sizes.sort(key=lambda x: (-x[1], x[0]))
176
+ if print_mode > 0:
177
+ if print_mode > 1: print('- ' * 20)
178
+ for package_name, package_size in package_sizes:
179
+ print(f"{package_name}: {human_readable_size(package_size)}")
180
+ return package_sizes
181
+
182
+
183
+ class ProgressBar:
184
+ """ 对运行可能需要较长时间的任务,添加进度条显示
185
+
186
+ # 示例用法
187
+ with ProgressBar(100) as pb:
188
+ for i in range(100):
189
+ time.sleep(0.1) # 模拟耗时工作
190
+ pb.progress = i + 1 # 更新进度
191
+ """
192
+
193
+ def __init__(self, total):
194
+ self.total = total # 总进度
195
+ self.progress = 0 # 当前进度
196
+ self.stop_flag = False # 停止标志
197
+
198
+ def __enter__(self):
199
+ # 启动进度显示线程
200
+ self.progress_thread = Thread(target=self.display_progress)
201
+ self.progress_thread.start()
202
+ return self
203
+
204
+ def __exit__(self, exc_type, exc_val, exc_tb):
205
+ # 强制将进度设置为100%
206
+ self.progress = self.total
207
+ # 停止进度显示线程
208
+ self.stop_flag = True
209
+ self.progress_thread.join()
210
+
211
+ def display_progress(self):
212
+ with tqdm(total=self.total) as pbar:
213
+ while not self.stop_flag:
214
+ pbar.n = self.progress
215
+ pbar.refresh()
216
+ time.sleep(1)
217
+ pbar.n = self.progress
218
+ pbar.refresh()
219
+
220
+
221
+ class BitMaskTool:
222
+ """ 二进制位掩码工具
223
+
224
+ 概念术语
225
+ bitval,每一个位上的具体取值,0或1
226
+ bitsum,所有位上的取值的和,即二进制数的十进制表示
227
+ """
228
+
229
+ def __init__(self, bit_names=None, bitsum_counter=None):
230
+ """ 初始化 BitMaskTool 对象
231
+
232
+ :param list bit_names: 每一位功能开启时,显示的标签名。未输入时,填充0,1,2,3...注意总数,要按域宽对齐
233
+ :param dict/list bitsum_counter: 各种值出现的次数,可以是一个字典或者一个包含出现次数的列表
234
+ """
235
+ # 1 每一位功能开启时,显示的标签名。未输入时,填充0,1,2,3...注意总数,要按域宽对齐
236
+ if bit_names is None:
237
+ bit_names = list(aligned_range(len(bit_names)))
238
+ self.bit_names = bit_names
239
+ # 2 各种值出现的次数
240
+ if isinstance(bitsum_counter, (list, tuple)):
241
+ bitsum_counter = Counter(bitsum_counter)
242
+ self.bitsum_counter = bitsum_counter or {}
243
+
244
+ def get_bitsum_names(self, bitsum):
245
+ """ 从bitsum得到names的拼接
246
+
247
+ >> get_bitsum_names(3)
248
+ '语义定位,图表'
249
+ """
250
+ if not isinstance(bitsum, int):
251
+ try:
252
+ bitsum = int(bitsum)
253
+ except ValueError:
254
+ bitsum = 0
255
+
256
+ tags = []
257
+ for i, k in enumerate(self.bit_names):
258
+ if (1 << i) & bitsum:
259
+ tags.append(k)
260
+ return ','.join(tags)
261
+
262
+ def count_bitsum_relations(self, target_bitsum, relation='='):
263
+ """ 计算特定关系的 bitsum 数量
264
+
265
+ :param int target_bitsum: 目标 bitsum
266
+ :param str relation: 关系类型,可以是 '=', '⊂', '⊃'
267
+ 假设bitval对应的bit_names为n1,n2,n3,n4。
268
+ 那么bitsum相当于是bitval的一个集合
269
+ 比如a={n1,n3,n4},b={n1,n3},因为a完全包含b,所以认为a⊃b,或者a⊋b、b⊂a、b⊊a
270
+ :return int: 符合条件的 bitsum 数量
271
+ """
272
+ count = 0
273
+ if relation == '=':
274
+ # 直接计算等于 target_bitsum 的数量
275
+ count = self.bitsum_counter.get(target_bitsum, 0)
276
+ elif relation == '⊂':
277
+ # 计算所有被 target_bitsum 包含的 bitsum 的数量
278
+ for bitsum, num in self.bitsum_counter.items():
279
+ if bitsum and bitsum & target_bitsum == bitsum:
280
+ count += num
281
+ elif relation == '⊃':
282
+ # 计算所有包含 target_bitsum 的 bitsum 的数量
283
+ for bitsum, num in self.bitsum_counter.items():
284
+ if bitsum & target_bitsum == target_bitsum:
285
+ count += num
286
+ return count
287
+
288
+ def check_bitflag(self, max_bitsum_len=None, reletion='=',
289
+ filter_zero=False, sort_by=None, *,
290
+ min_bitsum_len=0):
291
+ """ 检查并返回 bitsum 关系的 DataFrame
292
+
293
+ :param int max_bitsum_len: 最大 bitsum 长度
294
+ :param str reletion: 关系类型,可以是 '=', '⊂', '⊃'
295
+ 支持输入多个字符,表示要同时计算多种关系
296
+ :param bool filter_zero: 是否过滤掉零值
297
+ :param None|str sort_by: 排序字段
298
+ None, 默认排序
299
+ count, 按照数量从大到小排序
300
+ bitsum, 按照 bitsum 从小到大排序
301
+ :param int min_bitsum_len: 最小 bitsum 长度
302
+ :return: 包含 bitsum 关系的 DataFrame
303
+ """
304
+ from itertools import combinations
305
+
306
+ total = sum(self.bitsum_counter.values())
307
+ rows, columns = [], ['类型', '名称', '百分比.次数']
308
+ rows.append([-1, '总计', total])
309
+
310
+ if max_bitsum_len is None:
311
+ max_bitsum_len = len(self.bit_names)
312
+
313
+ bitvals = [(1 << i) for i in range(len(self.bit_names))]
314
+ for m in range(min_bitsum_len, max_bitsum_len + 1):
315
+ for comb in combinations(bitvals, m):
316
+ bitsum = sum(comb)
317
+ count = self.count_bitsum_relations(bitsum, relation=reletion)
318
+ if filter_zero and count == 0:
319
+ continue
320
+ rows.append([f'{reletion}{bitsum}',
321
+ self.get_bitsum_names(bitsum),
322
+ count])
323
+
324
+ if sort_by == 'count':
325
+ rows.sort(key=lambda x: x[2], reverse=True)
326
+ elif sort_by == 'bitsum':
327
+ rows.sort(key=lambda x: int(x[0][1:]) if isinstance(x[0], str) else x[0])
328
+
329
+ df = pd.DataFrame.from_records(rows, columns=columns)
330
+ df['百分比.次数'] = percentage_and_value(df['百分比.次数'], 2, total=total)
331
+ return df
332
+
333
+ def report(self):
334
+ """ 生成统计报告 """
335
+ html_content = []
336
+
337
+ html_content.append('<h1>1 包含每一位bitval特征的数量</h1>')
338
+ df1 = self.check_bitflag(1, '⊃')
339
+ html_content.append(df1.to_html())
340
+
341
+ html_content.append('<h1>2 每一种具体bitsum组合的数量</h1>')
342
+ df2 = self.check_bitflag(reletion='=', filter_zero=True, sort_by='bitsum')
343
+ html_content.append(df2.to_html())
344
+
345
+ return '\n'.join(html_content)
346
+
347
+
348
+ def loguru_setup_jsonl_logfile(logger, log_dir, rotation_size="10 MB"):
349
+ """
350
+ 给loguru的日志器添加导出文件的功能,使用jsonl格式
351
+
352
+ :param logger: 日志记录器,一般是from loguru import logger的logger
353
+ :param log_dir: 存储日志的目录,因为有多个文件,这里要输入的是所在的目录
354
+ :param rotation_size: 文件多大后分割
355
+ :return:
356
+ """
357
+ from datetime import datetime
358
+
359
+ os.makedirs(log_dir, exist_ok=True) # 自动创建日志目录
360
+
361
+ # 日志文件名匹配的正则表达式,格式为 年月日_时分秒.log
362
+ log_filename_pattern = re.compile(r"(\d{8}_\d{6})\.jsonl")
363
+
364
+ # 找到最新的日志文件
365
+ def find_latest_log_file(log_dir):
366
+ log_files = []
367
+ for file in os.listdir(log_dir):
368
+ if log_filename_pattern.match(file):
369
+ log_files.append(file)
370
+
371
+ if log_files:
372
+ # 根据时间排序,选择最新的日志文件
373
+ log_files.sort(reverse=True)
374
+ return os.path.join(log_dir, log_files[0])
375
+ return None
376
+
377
+ # 检查是否有未写满的日志文件
378
+ latest_log_file = find_latest_log_file(log_dir)
379
+
380
+ if latest_log_file:
381
+ log_path = latest_log_file
382
+ else:
383
+ # 生成新的日志文件名
384
+ log_filename = datetime.now().strftime("%Y%m%d_%H%M%S") + ".jsonl"
385
+ log_path = os.path.join(log_dir, log_filename)
386
+
387
+ # 配置 logger,写入日志文件,设置旋转条件,使用 JSON 序列化
388
+ logger.add(log_path, rotation=rotation_size, serialize=True)
389
+
390
+ # 输出初始化成功信息
391
+ logger.info(f"日志系统已初始化,日志文件路径:{log_path}")
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # @Author : 陈坤泽
4
+ # @Email : 877362867@qq.com
5
+ # @Date : 2020/06/01 18:13
6
+
7
+ import copy
8
+
9
+ from pyxllib.prog.pupil import dprint, prettifystr
10
+ from pyxllib.prog.specialist.browser import Explorer
11
+ from pyxllib.algo.pupil import intersection_split
12
+ from pyxllib.file.specialist import File, Dir, filesmatch, get_encoding, XlPath
13
+
14
+
15
+ # 需要使用的第三方软件
16
+ # BCompare.exe, bcompare函数要用
17
+
18
+ class BCompare(Explorer):
19
+ def __init__(self, app='bcomp', shell=False):
20
+ """
21
+ 240512周日20:06,本来写的是BCompare,但是友鑫mac电脑上发现似乎有问题,所以改成bcomp,这种在windows上也能用
22
+ """
23
+ super().__init__(app, shell)
24
+
25
+ @classmethod
26
+ def to_bcompare_files(cls, *args, files=None):
27
+ """ 这个需要一次性获得所有的数据,才适合分析整体上要怎么获取对应的多个文件
28
+
29
+ :param files: 每个arg对应的文件名,默认按 'left'、'right', 'base' 来生成
30
+ 也可以输入一个list[str],表示多个args依次对应的文件名
31
+ filename的长度可以跟args不一致,多的不用,少的自动生成
32
+ """
33
+ # 1 如果oldfile和newfile都是dict、set、list、tuple,则使用特殊方法文本化
34
+ # 如果两个都是list,不应该提取key后比较,所以限制第1个类型必须是dict或set,然后第二个类型可以适当放宽条件
35
+ # if not oldfile: oldfile = str(oldfile)
36
+ if len(args) > 1 and isinstance(args[0], (dict, set)) and isinstance(args[1], (dict, set, list, tuple)):
37
+ args = copy.copy(list(args))
38
+ t = [prettifystr(li) for li in intersection_split(args[0], args[1])]
39
+ args[0] = f'【共有部分】,{t[0]}\n\n【独有部分】,{t[1]}'
40
+ args[1] = f'【共有部分】,{t[2]}\n\n【独有部分】,{t[3]}'
41
+
42
+ # 2 参数对齐
43
+ if not isinstance(files, (list, tuple)):
44
+ files = [files]
45
+ if len(files) < len(args):
46
+ files += [None] * (len(args) - len(files))
47
+ ref_names = ['left', 'right', 'base']
48
+
49
+ # 3 将每个参数转成一个文件
50
+ new_args = []
51
+ default_suffix = None
52
+ for i, arg in enumerate(args):
53
+ f = XlPath.safe_init(arg)
54
+ if f is not None and f.is_file(): # 是文件对象,且存在
55
+ new_args.append(f)
56
+ if not default_suffix:
57
+ default_suffix = f.suffix
58
+ # elif isinstance(f, File): # 文本内容也可能生成合法的伪路径,既然找不到还是统一按字符串对比差异好
59
+ # # 是文件对象,但不存在 -> 报错
60
+ # raise FileNotFoundError(f'{f}')
61
+ else: # 不是文件对象,要转存到文件
62
+ if not files[i]: # 没有设置文件名则生成一个
63
+ files[i] = XlPath.init(ref_names[i], XlPath.tempdir(), suffix=default_suffix)
64
+ else:
65
+ files[i] = XlPath(files[i])
66
+ files[i].write_text(str(arg))
67
+ new_args.append(files[i])
68
+
69
+ return new_args
70
+
71
+ def __call__(self, *args, wait=True, files=None, sameoff=False, **kwargs):
72
+ r"""
73
+ :param wait:
74
+ 一般调用bcompare的时候,默认值wait=True,python是打开一个子进程并等待bcompare软件关闭后,才继续执行后续代码。
75
+ 如果你的业务场景并不需要等待bcompare关闭后执行后续python代码,可以设为wait=False,不等待。
76
+ :param sameoff: 如果设为True,则会判断内容,两份内容如果相同则不打开bc
77
+ 这个参数一定程度会影响性能,非必要的时候不要开。
78
+ 或者在外部的时候,更清楚数据情况,可以在外部判断内容不重复再跑bcompare函数
79
+ 这个目前的实现策略,是读取文件重新判断的,会有性能开销,默认关闭该功能
80
+ :return: 程序返回被修改的oldfile内容
81
+ 注意如果没有修改,或者wait=False,会返回原始值
82
+ """
83
+ files = self.to_bcompare_files(*args, files=files)
84
+
85
+ if sameoff and len(files) > 1:
86
+ if files[0].read_auto() == files[1].read_auto():
87
+ return
88
+ super().__call__(*([str(f) for f in files]), wait=wait, **kwargs)
89
+ # bc软件操作中可能会修改原文内容,所以这里需要重新读取,不能用前面算过的结果
90
+ return XlPath(files[0]).read_auto()
91
+
92
+
93
+ bcompare = BCompare() # nowatch: 调试阶段,不需要自动watch的变量
94
+
95
+
96
+ def modify_file(file, func, *, outfile=None, file_mode=None, debug=0):
97
+ """ 对单个文件就行优化的功能函数
98
+
99
+ 这样一些底层函数功能可以写成数据级的接口,然后由这个函数负责文件的读写操作,并且支持debug比较前后内容差异
100
+
101
+ :param outfile: 默认是对file原地操作,如果使用该参数,则将处理后的内容写入outfile文件
102
+ :param file_mode: 指定文件读取类型格式,例如'.json'是json文件,读取为字典
103
+ :param debug: 这个功能可以对照refine分级轮理解
104
+ 无outfile参数时,原地操作
105
+ 0,【直接原地操作】关闭调试,直接运行 (根据outfile=None选择原地操作,或者指定生成新文件)
106
+ 1,【进行审核】打开BC比较差异,左边原始内容,右边参考内容 (打开前后差异内容对比)
107
+ -1,【先斩后奏】介于完全不调试和全部人工检查之间,特殊的差异比较。左边放原始文件修改后的结果,右边对照原内容。
108
+ 有outfile参数时
109
+ 0 | False,直接生成目标文件,如果outfile已存在会被覆盖
110
+ 1 | True,直接生成目标文件,但是会弹出bc比较前后内容差异 (相同内容不会弹出)
111
+ """
112
+ infile = File(file)
113
+ enc = get_encoding(infile.read(mode='b'))
114
+ data = infile.read(mode=file_mode, encoding=enc)
115
+ origin_content = str(data)
116
+ new_data = func(data)
117
+
118
+ isdiff = origin_content != str(new_data)
119
+ if outfile is None: # 原地操作
120
+ if isdiff: # 内容不同才会有相关debug功能,否则静默跳过就好
121
+ if debug == 0:
122
+ infile.write(new_data, mode=file_mode) # 直接处理
123
+ elif debug == 1:
124
+ temp_file = File('refine_content', Dir.TEMP, suffix=infile.suffix).write(new_data)
125
+ bcompare(infile, temp_file) # 使用beyond compare软件打开对比查看
126
+ elif debug == -1:
127
+ temp_file = File('origin_content', Dir.TEMP, suffix=infile.suffix)
128
+ infile.copy(temp_file)
129
+ infile.write(new_data, mode=file_mode, encoding=enc) # 把原文件内容替换了
130
+ bcompare(infile, temp_file) # 然后显示与旧内容进行对比
131
+ else:
132
+ raise ValueError(f'{debug}')
133
+ else:
134
+ outfile = File(outfile)
135
+ outfile.write(new_data, mode=file_mode, encoding=enc) # 直接处理
136
+ if debug and isdiff:
137
+ bcompare(infile, outfile)
138
+
139
+ return isdiff
140
+
141
+
142
+ class PairContent:
143
+ """ 配对文本类,主要用于bc差异比较 """
144
+
145
+ def __init__(self, left_file_name=None, right_file_name=None):
146
+ self.left_file = File(left_file_name) if left_file_name else left_file_name
147
+ self.right_file = File(right_file_name) if right_file_name else right_file_name
148
+ self.left, self.right = [], []
149
+
150
+ def add(self, lt, rt=None):
151
+ """ rt不加,默认本轮内容是同lt """
152
+ lt = str(lt)
153
+ self.left.append(lt)
154
+ rt = lt if rt is None else str(rt)
155
+ self.right.append(rt)
156
+
157
+ def bcompare(self, **kwargs):
158
+ left, right = '\n'.join(self.left), '\n'.join(self.right)
159
+ if self.left_file is not None:
160
+ left = self.left_file.write(left)
161
+ if self.right_file is not None:
162
+ right = self.right_file.write(right)
163
+ bcompare(left, right, **kwargs)
164
+
165
+
166
+ def filetext_replace(files, func, *,
167
+ count=-1, start=1, bc=False, write=False, if_exists=None):
168
+ r"""遍历目录下的文本文件进行批量处理的功能函数
169
+
170
+ :param files: 文件匹配规则,详见filesmatch用法
171
+ :param func: 通用文本处理函数
172
+ :param count: 匹配到count个文件后结束,防止满足条件的文件太多,程序会跑死
173
+ :param start: 从编号几的文件开始查找,一般在遇到意外调试的时候使用
174
+ :param bc: 使用beyond compare软件
175
+ 注意bc的优先级比write高,如果bc和write同时为True,则会开bc,但并不会执行write
176
+ :param write: 是否原地修改文件内容进行保存
177
+ :param if_exists: 是否进行备份,详见writefile里的参数文件
178
+ :return: 满足条件的文件清单
179
+ """
180
+ ls = []
181
+ total = 0
182
+ for f in filesmatch(files):
183
+ # if 'A4-Exam' in f:
184
+ # continue
185
+ total += 1
186
+ if total < start:
187
+ continue
188
+ s0 = File(f).read()
189
+ s1 = func(s0)
190
+ if s0 != s1:
191
+ match = len(ls) + 1
192
+ dprint(f, total, match)
193
+ if bc:
194
+ bcompare(f, s1)
195
+ elif write: # 如果开了bc,程序是绝对不会自动写入的
196
+ File(f).write(s1, if_exists=if_exists)
197
+ ls.append(f)
198
+ if len(ls) == count:
199
+ break
200
+
201
+ match_num = len(ls)
202
+ dprint(total, match_num)
203
+ return ls