From f8f3eaffdfddc672c7ba9c457f026f25adc3bae5 Mon Sep 17 00:00:00 2001
From: old-tom <892955278@qq.com>
Date: Thu, 27 Apr 2023 14:00:59 +0800
Subject: [PATCH] first commit
---
.idea/.gitignore | 8 +
.idea/inspectionProfiles/Project_Default.xml | 22 ++
.../inspectionProfiles/profiles_settings.xml | 6 +
.idea/misc.xml | 4 +
.idea/modules.xml | 8 +
.idea/py-xltpl.iml | 8 +
Pipfile | 11 +
Pipfile.lock | 20 ++
exceltpl/__init__.py | 7 +
exceltpl/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 135 bytes
exceltpl/excel_writer.py | 46 +++
exceltpl/pdman/__init__.py | 7 +
.../pdman/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 141 bytes
.../__pycache__/pdman_reader.cpython-38.pyc | Bin 0 -> 3700 bytes
exceltpl/pdman/pdman_reader.py | 117 ++++++++
futool/__init__.py | 0
futool/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 133 bytes
futool/cache/__init__.py | 0
futool/core/__init__.py | 0
.../core/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 138 bytes
.../__pycache__/fh_file_path.cpython-38.pyc | Bin 0 -> 3488 bytes
futool/core/fh_file_path.py | 144 +++++++++
futool/core/fu_date.py | 273 ++++++++++++++++++
futool/core/fu_file.py | 230 +++++++++++++++
futool/core/fu_lang.py | 7 +
futool/core/fu_math.py | 7 +
futool/core/fu_parser.py | 7 +
futool/db/__init__.py | 0
futool/db/fh_db.py | 8 +
futool/http/__init__.py | 0
futool/http/http_downloader.py | 94 ++++++
futool/http/http_request.py | 158 ++++++++++
futool/http/http_response.py | 79 +++++
futool/net/__init__.py | 0
futool/poi/__init__.py | 7 +
futool/poi/fu_excel.py | 213 ++++++++++++++
futool/system/__init__.py | 0
futool/system/fu_sys.py | 85 ++++++
futool/validator/__init__.py | 0
requirements.txt | 1 +
40 files changed, 1577 insertions(+)
create mode 100644 .idea/.gitignore
create mode 100644 .idea/inspectionProfiles/Project_Default.xml
create mode 100644 .idea/inspectionProfiles/profiles_settings.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/modules.xml
create mode 100644 .idea/py-xltpl.iml
create mode 100644 Pipfile
create mode 100644 Pipfile.lock
create mode 100644 exceltpl/__init__.py
create mode 100644 exceltpl/__pycache__/__init__.cpython-38.pyc
create mode 100644 exceltpl/excel_writer.py
create mode 100644 exceltpl/pdman/__init__.py
create mode 100644 exceltpl/pdman/__pycache__/__init__.cpython-38.pyc
create mode 100644 exceltpl/pdman/__pycache__/pdman_reader.cpython-38.pyc
create mode 100644 exceltpl/pdman/pdman_reader.py
create mode 100644 futool/__init__.py
create mode 100644 futool/__pycache__/__init__.cpython-38.pyc
create mode 100644 futool/cache/__init__.py
create mode 100644 futool/core/__init__.py
create mode 100644 futool/core/__pycache__/__init__.cpython-38.pyc
create mode 100644 futool/core/__pycache__/fh_file_path.cpython-38.pyc
create mode 100644 futool/core/fh_file_path.py
create mode 100644 futool/core/fu_date.py
create mode 100644 futool/core/fu_file.py
create mode 100644 futool/core/fu_lang.py
create mode 100644 futool/core/fu_math.py
create mode 100644 futool/core/fu_parser.py
create mode 100644 futool/db/__init__.py
create mode 100644 futool/db/fh_db.py
create mode 100644 futool/http/__init__.py
create mode 100644 futool/http/http_downloader.py
create mode 100644 futool/http/http_request.py
create mode 100644 futool/http/http_response.py
create mode 100644 futool/net/__init__.py
create mode 100644 futool/poi/__init__.py
create mode 100644 futool/poi/fu_excel.py
create mode 100644 futool/system/__init__.py
create mode 100644 futool/system/fu_sys.py
create mode 100644 futool/validator/__init__.py
create mode 100644 requirements.txt
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..f7476a6
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..8ce926d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..902a83d
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/py-xltpl.iml b/.idea/py-xltpl.iml
new file mode 100644
index 0000000..d0876a7
--- /dev/null
+++ b/.idea/py-xltpl.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 0000000..22d660a
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,11 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+
+[dev-packages]
+
+[requires]
+python_version = "3.8"
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 0000000..e42812c
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,20 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "7f7606f08e0544d8d012ef4d097dabdd6df6843a28793eb6551245d4b2db4242"
+ },
+ "pipfile-spec": 6,
+ "requires": {
+ "python_version": "3.8"
+ },
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {},
+ "develop": {}
+}
diff --git a/exceltpl/__init__.py b/exceltpl/__init__.py
new file mode 100644
index 0000000..44c5fa6
--- /dev/null
+++ b/exceltpl/__init__.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2023/4/27 6:21
+# @Author : old tom
+# @File : __init__.py.py
+# @Project : py-xltpl
+# @Desc :
diff --git a/exceltpl/__pycache__/__init__.cpython-38.pyc b/exceltpl/__pycache__/__init__.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d63867036ba4a987f526d671d82c09585434bd50
GIT binary patch
literal 135
zcmWIL<>g`kf}SOwDNBL$V-N=!FakLaKwQiMBvKfH88jLFRx%WUgb~Cq9T%&Z
zn1afZjQl(xrCX6xQjimqT9KRzX2i#5=4F<|$LkeT-r}&y%}*)KNwou+@)?L30D~bP
AJpcdz
literal 0
HcmV?d00001
diff --git a/exceltpl/excel_writer.py b/exceltpl/excel_writer.py
new file mode 100644
index 0000000..9b59d7d
--- /dev/null
+++ b/exceltpl/excel_writer.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2023/4/27 10:01
+# @Author : old tom
+# @File : excel_writer.py
+# @Project : py-xltpl
+# @Desc : 写入excel模板
+from xltpl.writerx import BookWriter
+from exceltpl.pdman.pdman_reader import PDmanReader
+from futool.core.fh_file_path import exist
+
+
+class ATiDeDBDocWriter(object):
+ class TemplateNotExistError(Exception):
+ def __init__(self, msg):
+ Exception.__init__(self, msg)
+
+ def __init__(self, data, tpl_path, out_path):
+ """
+ :param data: 数据 使用PDmanReader 读取
+ :param tpl_path: 模板路径
+ :param out_path: 输出路径
+ """
+ if not exist(tpl_path):
+ raise self.TemplateNotExistError(f'[{tpl_path}]模板不存在,请检查路径')
+ self.writer = BookWriter(tpl_path)
+ self.writer.set_jinja_globals(dir=dir, getattr=getattr)
+ self.data = data
+ self.out_path = out_path
+
+ def fill(self):
+ payloads = []
+ for i, t in enumerate(self.data):
+ table = self.data[t]
+ table['sheet_name'] = str(i + 1) + '.' + table['table_name']
+ table['tpl_idx'] = i
+ payloads.append(table)
+ self.writer.render_sheets(payloads=payloads)
+ self.writer.save(self.out_path)
+
+
+if __name__ == '__main__':
+ reader = PDmanReader(r'D:\文档\工作\ATD\2023上半年\项目\昭通OA\表结构\昭通OA.pdma.json')
+ writer = ATiDeDBDocWriter(reader.read_by_module('OA_CAR'), r'C:\Users\89295\Desktop\atide_db_tpl.xlsx',
+ r'C:\Users\89295\Desktop\车辆管理.xlsx')
+ writer.fill()
diff --git a/exceltpl/pdman/__init__.py b/exceltpl/pdman/__init__.py
new file mode 100644
index 0000000..44c5fa6
--- /dev/null
+++ b/exceltpl/pdman/__init__.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2023/4/27 6:21
+# @Author : old tom
+# @File : __init__.py.py
+# @Project : py-xltpl
+# @Desc :
diff --git a/exceltpl/pdman/__pycache__/__init__.cpython-38.pyc b/exceltpl/pdman/__pycache__/__init__.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..210886784c12ecb2cae9eab104aa95aa9679c0a3
GIT binary patch
literal 141
zcmWIL<>g`kf}SOwDNBL$V-N=!FakLaKwQiMBvKfH88jLFRx%WUgb~CqBNwZf
zn1afZjQl(xrCX6xQjimqT9KRzW)!64Cg#P&$7kkcmc+;F6;$5hu*uC&Da}c>1DW+1
Gh#3G{-yyF6
literal 0
HcmV?d00001
diff --git a/exceltpl/pdman/__pycache__/pdman_reader.cpython-38.pyc b/exceltpl/pdman/__pycache__/pdman_reader.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..aa0967d252fddab7fdaf8d4192035a0d82b61b0c
GIT binary patch
literal 3700
zcmb_f-EZ606~C9HDC)zG;y7`XW|`N0nS*1vPUE#+Fx0DzEFG+PjaJFYVaY?gJ``JnEdNEH_`1kR{((Ih*vC1SvLe|Y_OMal
zO=l7E
z+k|Ocy`@?jPq4(QB8p76bgpsz7PXQ*xoNOun_4NJ<|$_6$t~2NTp(uZ8R#V@jS~Du
zH3*Ak6#K6%2(Q5%gwSweQAR98l>XI#UCszPn>#XvQL_Md2-f_
zw8H$#LNn9b{gT^l9spq^f(hGj$
zvdgW+qVK_sQE)=OQia*XB_0%gculn&T1J5f&=$&
zr8^h)k!-KS9ps=1$tG=)7Hum?whQDwE#6n6)WVH2uZLCNiwxVYdezXjVOR^eTZzvIs>DYWNQm)+o}4R7=q0&;u+-O
zXmjGfeDKuHDT?EiaDVJl&m>bFNl)ej;RVfoRyCK1N}UrmHq5?
z5|#xRr3p3Y2+h%BG)E)RI22r3KB%7JSmW$&EC{n8dLM6rL5{=7Wx|WAvHitz$hBi^I#N~+)!I;Jbs2;
zpKYmN0;BPBc=2lpFLkF
zc-Ro$TyG#Y@}L)4V9~G|R(TL9RR*B*3WoAXhYkjIy;9{a3rsaihYo~w`9fGz)T2?R*|;K*8+iz7UL)%uwxZ=+4tV!B7}8l&nVOP1(1%tP^{TiV2&7#YkD<{brNOH;QLc!HV1cq1(=9kd3Xqla_n&gRock7AoV`TXO~U*GLb
z&F!{{m42(r-##n+Mm?}Hf>##0<`5I05?pW`ADV`WqvD*{(xI+(%Sx_Yr*ueuZ04z=b)nz1-1PA&f>4DONsLa6T-lum_dG|9Q
zcITsydgFT_5#NAq^7F;Fpl2#Ej}Ah4GH8!fJx-e^_5#!&j|L!;fg5y4x=aC)u5?LqmjW~@1Zx$^
za|?>YRZYxDsN@L11k^c0ypKZyfg1`(T-9CI>W)FY=#D9(6;qArV}0w#2uqIbXnJ!W
zO<(TOq=_&8gC?V^S-!PjGb9ltd@7I0M`h`q@3~8b26843Wvx5~%#0CFkH?_J2&l55
zJb{%b*s!NynY*F2q0+u?NQXICwDj0k0=+QO(0q~VLKaB`Wyj^)bYh#D+jLU$AWD|~
zS`Fg?)^ywSdsfo(!(zjABYG*KMN@+c-;*V%UcQP?fxNX050+rm0tfmo48)XBgAi(G
zrw%!b?Rb`n%M}p{%*(qOgL%b)J2(yv0f$OMZW*N$^hLO*L7JpG$^A2}HeVRv&|usv
zf-@s;%&ZuNhd7K4hA#0kHkh|$HjP6k7JuR;Y|xqJbqw%s@nB2=nS1Z=f`U+Vnj504*hl;&3orJ3oO
zQ;)y6(^M6|pmbS8R
zVFj$qgV4-GM=Ooc_uZ+oFZfjDD&z&oCv_*h8kZ7SSi*mr%hxzKFCI?fWa)J#bG{x|
s#u5}Og;j)16j7oMe?`LmaEXjFagO;umQn$hds%9zIR=$0_ 0:
+ f['type'] = self.domains[f['domain']]
+ elif len(str(f['len'])) > 0 and len(str(f['scale'])) == 0:
+ f['type'] = f['type'] + '(' + str(f['len']) + ')'
+ elif len(str(f['len'])) > 0 and len(str(f['scale'])) > 0:
+ f['type'] = f['type'] + '(' + str(f['len']) + ',' + str(f['scale']) + ')'
+ rt.append({'enName': f['defKey'],
+ 'chName': f['defName'] + ';' + f['comment'] if len(f['comment']) > 0 else f['defName'],
+ 'type': f['type'], 'nullable': 'Y' if f['notNull'] else 'N'})
+ return rt
+
+
+#
+if __name__ == '__main__':
+ pdfile = r'D:\文档\工作\ATD\2023上半年\项目\昭通OA\表结构\昭通OA.pdma.json'
+ reader = PDmanReader(pdfile)
+ all_table = reader.read_by_module('TEST')
+ print(all_table['test'])
diff --git a/futool/__init__.py b/futool/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/futool/__pycache__/__init__.cpython-38.pyc b/futool/__pycache__/__init__.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f0ed9aada95d568a3b8e1d7e66c00627661dba7b
GIT binary patch
literal 133
zcmWIL<>g`kf|<4oDIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2DD#VRH_KP5G$
zpt2+*KMzRhR^*fv#KgyE=4F<|$LkeT-r}&y%}*)KNwou+@fnC20J2^k
AO8@`>
literal 0
HcmV?d00001
diff --git a/futool/cache/__init__.py b/futool/cache/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/futool/core/__init__.py b/futool/core/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/futool/core/__pycache__/__init__.cpython-38.pyc b/futool/core/__pycache__/__init__.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..249a2177b5da09526710545109148fd1bdbff215
GIT binary patch
literal 138
zcmWIL<>g`kf|<4oDIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2DX#VRH_KP5G$
zpt2+*KMzRhR^*fv0Og8OW8&j8^D;}~=2Of$%I31Bc2Lr7e9N(ua^rK<2scWS0{_Qcn{|>?@4d9&Hyb;fI0zVP$Mg7R=J&mRGhel}
zg(WD{A4lnAizNMx9e+`wa{@kl0V*m{l9l4#KZz63m*etjiON(Nm8fDWWhJhfQbwcd
z9YQr47**o|8l?KD6c5r64a2`qTWBl%hfFyW7IJMg0&*=hN;~PcQ4(*Zhs-v*9d_uV
zJ4Us5#B3LGJLxWv>o7Y-Z#Ufoy={%t_R`m&zn$)*`(gJkdVn5;{~eT^m12ii0l(Cn
z#M)mJ=$wGh-V2o}p-yp`lDI+xRG}(hQK<&&gS_)h!WrnboSxyd?es8avEm+B6OKYz
z`SYRsbad(Qr2F8ed*^pC#-*eT^YwylH0G!9*&R?hQiYVH5~<1r3<^h(-k8ioJ;O<}
z;H0fQ*9{|`PdkPI!>Vm&Q(Vc}=Vm3Q!|H$EJ=xutw5Zuv7;y%yJp8>moOKG>zEsh%
ztSrnivoAGZq|#Z_C;;?hg%PH~%Iye1AR`y|HtBS%erRuuFf>j~Lm>G?&NK`jHjJD_
zi?E}?TMgrUF_B%H2^t2ql7=CU5D0x$2=-xkWCYJfE9gX3#Yl|s05hE;%O?qp*9*4s
zbYU%Ao;M
z8}Lr`Ix&R@aVnec=PKGH9%QC%WiObsgsV8SNgLj_Hg6m^68-ir0OQ#-z|g+W*QHhH
zk^wzRQWXP}6uax1)NVd>r|!KvblI}mHrRw~X&b<>PH1n!%XYw66uvr2?7tGqQ?Ni7
zu6AQ=Y4K5Q{D#+XXXZWJzR-V>u~`>v=0Ogam`+<_=9-v``B0;spN|@C;Exl?E3~j&
z`~oHfc6Yq8Iz7_>ezf*rX8HD<`{XD0*_GAmA_mUvfFKq9{<>A0xLmt4;S4)R-#7-fC%G|A
z)-{f6>|ECBkI9}NF@12a&02S*(s}BUQ2z*;5`{i&*dWOEL3{N-$oEr8)VyA>jcg?d
z0PR$FYzEl5L`pK1Clzq80uEM-KZ1aO>yF=c7Z(~1cE7*sUU}p`nDXesuY281-O+}0
zvk;J-2?BS^tJ72d#Ud~bYY^r{&aT}1cKP;=W`lj{5eqZ(j?GovjqCNbo8l)oi1DK^
zyi?dgtfE*U*LxL`C~wJHR>8;(VkGszfEP(RI}B|IzKCG^q?LCP>AcA{STq144U019
zI8sR}6T8D9l$6Og#E~lslF}umq@0w#?!BZsaz?Faa7n3E`I07jfs$4w>~kkr($EWK
z@WJ^5C7Jb?t8lQe)9U5x?vv{c+xxBCaE*U(qh{S}(d^F6
zyVKr%yGA-72O(ypiV)T-V}H1Fx0dE7R-TR37H69MQ``-3fB|Ziu?CXHEglOo&^q?JTu6`qHS=7t9k$L8T%#;~!35%i;dEiEYl*j~
zZDZ|7VT1wLtW+D#^%aCH6LmZ@54L-rieTiM<2D7ky`8
zKMCK4g6}JzWh$g9H6BYxU`|-Zee#1lG3mF=^Z1=B$sy{CNkK${gOtJ$EyISM<{En+
z+OLG>P#w*Hl`~%oi;9w4fnguOUe|nJun+#+4}YidMZMFDM|^PbfHP7^=g%>GfO=Vt
zSFCujiT9D0?s(bo&}kF+GvQcFTu_2vxr#|EGLEbL@Wy5sp(1p|tuEXotRYZs1()j|
idGF^>(3186s4%Z}r6bf4(#5YM9B%2@9SKEnME@T`(t0QW
literal 0
HcmV?d00001
diff --git a/futool/core/fh_file_path.py b/futool/core/fh_file_path.py
new file mode 100644
index 0000000..e358267
--- /dev/null
+++ b/futool/core/fh_file_path.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2022/9/12 21:03
+# @Author : old tom
+# @File : fh_file_path.py
+# @Project : Futool
+# @Desc : 文件路径
+
+import pathlib
+import os
+import typing
+
+
+class PathNotExistError(Exception):
+ """
+ 路径不存在
+ """
+
+ def __init__(self, msg=''):
+ Exception.__init__(self, msg)
+
+
+def resolve_2_abs(path) -> pathlib.Path:
+ """
+ 相对路径解析为绝对路径
+ :param path:
+ :return:
+ """
+ return pathlib.Path(path).resolve()
+
+
+def isabs(path):
+ """
+ 是否绝对路径
+ :param path:
+ :return:
+ """
+ return os.path.isabs(path)
+
+
+def exist(path):
+ """
+ 文件或文件夹是否存在
+ :param path:
+ :return:
+ """
+ return os.path.exists(path)
+
+
+def rm_dir(dir_path):
+ """
+ 删除文件夹,此目录必须为空
+ :param dir_path:
+ :return:
+ """
+ pathlib.Path(dir_path).rmdir()
+
+
+def find_file_by_pattern(path, pattern) -> typing.Generator:
+ """
+ 根据匹配规则查找文件
+ :param path: 路径
+ :param pattern: 例:所有txt,*.txt
+ :return:
+ """
+ return pathlib.Path(path).rglob(pattern)
+
+
+def loop_mk_dir(dir_path, mode=0o777, exist_ok=False):
+ """
+ 创建文件夹及其子路径
+ :param dir_path: 文件夹路径
+ :param mode: 权限
+ :param exist_ok: 是否覆盖
+ :return:
+ """
+ pathlib.Path(dir_path).mkdir(parents=True, mode=mode, exist_ok=exist_ok)
+
+
+def loop_dir(dir_path, file_container: list, filter_fun=None):
+ """
+ 递归文件夹
+ :param dir_path:
+ :param file_container: 路径容器
+ :param filter_fun: 自定义过滤
+ :return:
+ """
+ if not exist(dir_path):
+ raise PathNotExistError('目标文件夹不存在')
+ file_list = os.listdir(dir_path)
+ for f in file_list:
+ full_path = os.path.join(dir_path, f)
+ if os.path.isdir(full_path):
+ loop_dir(full_path, file_container, filter_fun)
+ else:
+ if filter_fun:
+ if filter_fun(full_path):
+ file_container.append(full_path)
+ else:
+ file_container.append(full_path)
+ return file_container
+
+
+def is_windows_path(path) -> bool:
+ """
+ 是否windows路径
+ :param path:
+ :return:
+ """
+ return len(str(pathlib.Path(path).drive).rstrip()) > 0
+
+
+def parent_path_str(path) -> str:
+ """
+ 父级路径
+ :param path:
+ :return:
+ """
+ return str(parent_path(path))
+
+
+def parent_path(path) -> pathlib.Path:
+ """
+ 父级路径
+ :param path:
+ :return:
+ """
+ return pathlib.Path(path).parent
+
+
+def pwd():
+ """
+ 当前路径
+ :return:
+ """
+ return pathlib.Path().cwd()
+
+
+def home():
+ """
+ home路径
+ :return:
+ """
+ return pathlib.Path().home()
diff --git a/futool/core/fu_date.py b/futool/core/fu_date.py
new file mode 100644
index 0000000..0aef15b
--- /dev/null
+++ b/futool/core/fu_date.py
@@ -0,0 +1,273 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2022/9/10 10:17
+# @Author : old tom
+# @File : fu_date.py
+# @Project : Futool
+# @Desc : 日期时间工具
+
+import time
+from datetime import datetime, date, timedelta
+import calendar
+
+# 毫秒单位
+MILLISECOND_UNIT = 1000
+# 1小时3600秒
+ONE_HOUR_SECOND = 3600
+# 上午下午分界
+AM_PM_DIV = 11
+# 一周7天
+ONE_WEEK_DAYS = 7
+
+# 日期时间格式化转换
+FMT_MAPPING = {
+ 'yyyy-MM-dd': '%Y-%m-%d',
+ 'yyyyMMdd': '%Y%m%d',
+ 'yyyy-MM-dd hh:mm:ss': '%Y-%m-%d %I:%M:%S',
+ 'yyyy-MM-dd hh24:mm:ss': '%Y-%m-%d %H:%M:%S',
+ 'yyyy-MM-dd HH:mm:ss': '%Y-%m-%d %H:%M:%S'
+}
+
+
+def current_year() -> int:
+ """
+ 本年
+ :return:
+ """
+ return datetime.now().year
+
+
+def current_month() -> int:
+ """
+ 本月
+ :return:
+ """
+ return datetime.now().month
+
+
+def current_day() -> int:
+ return datetime.now().day
+
+
+def current_date() -> str:
+ return str(datetime.now().date())
+
+
+def current_datetime(fmt='yyyy-MM-dd hh24:mm:ss') -> str:
+ """
+ 获取当前日期时间
+ :param fmt:
+ :return:
+ """
+ return datetime.now().strftime(FMT_MAPPING[fmt])
+
+
+def current_time(fmt='s') -> int:
+ """
+ 获取当前时间绝对秒
+ :param fmt: s: 秒 ms:毫秒 ns:纳秒
+ :return:
+ """
+ time_jar = {
+ 's': int(time.time()),
+ 'ms': int(time.time() * MILLISECOND_UNIT),
+ 'ns': time.time_ns()
+ }
+ return time_jar[fmt]
+
+
+def current_timestamp():
+ """
+ 当前时间戳
+ :return:
+ """
+ return datetime.now().timestamp()
+
+
+def format_datetime_str(dt: str, fmt='yyyy-MM-dd hh24:mm:ss') -> datetime:
+ """
+ 格式化日期时间字符串
+ :param dt: 日期时间字符串
+ :param fmt: 格式化,例:yyyy-MM-dd
+ :return: datetime对象
+ """
+ return datetime.strptime(dt, FMT_MAPPING[fmt])
+
+
+def format_date_str(dt: str, fmt='yyyy-MM-dd') -> date:
+ """
+ 格式化日期字符串
+ :param dt: 日期字符串
+ :param fmt: yyyy-MM-dd
+ :return:
+ """
+ return datetime.strptime(dt, FMT_MAPPING[fmt]).date()
+
+
+def datetime_2_second(dt: str, fmt='yyyy-MM-dd hh24:mm:ss') -> int:
+ """
+ 日期时间字符串转绝对秒
+ :param dt: 日期时间字符串 yyyy-MM-dd hh:mm:ss 格式
+ :param fmt: 格式化函数
+ :return:
+ """
+ return int(format_datetime_str(dt, fmt).timestamp())
+
+
+def sec_2_datatime(sec_time: int, fmt='yyyy-MM-dd hh24:mm:ss') -> str:
+ """
+ 绝对秒转日期时间
+ :param sec_time:
+ :param fmt: 格式化函数
+ :return:
+ """
+ timed = time.localtime(sec_time)
+ return time.strftime(FMT_MAPPING[fmt], timed)
+
+
+def is_leap(year: int) -> bool:
+ """
+ 是否闰年
+ :param year:
+ :return:
+ """
+ return calendar.isleap(year)
+
+
+def begin_of_week(date_str: str, fmt='yyyy-MM-dd') -> str:
+ """
+ 周开始日期
+ :param date_str: 年
+ :param fmt: 格式化
+ :return:
+ """
+ formated_dt = format_date_str(date_str, fmt)
+ week_idx = weekday(date_str, fmt)
+ return date_str if week_idx == 0 else str(formated_dt - timedelta(days=week_idx))
+
+
+def end_of_week(date_str: str, fmt='yyyy-MM-dd') -> str:
+ """
+ 周结束日期
+ :param date_str: 年
+ :param fmt: 格式化
+ :return:
+ """
+ formated_dt = format_date_str(date_str, fmt)
+ week_idx = weekday(date_str, fmt)
+ return date_str if week_idx == 6 else str(formated_dt + timedelta(days=(6 - week_idx)))
+
+
+def end_of_month(y, m) -> int:
+ """
+ 月结束日期
+ :param y 年
+ :param m 月
+ :return:
+ """
+ return calendar.monthrange(y, m)[1]
+
+
+def weekday(date_str: str, fmt='yyyy-MM-dd') -> int:
+ """
+ 返回日期是周几
+ :param date_str: 年
+ :param fmt: 格式化
+ :return: 0-7 ,0:周一
+ """
+ fmted_date = format_datetime_str(date_str, fmt)
+ return calendar.weekday(fmted_date.year, fmted_date.month, fmted_date.day)
+
+
+def age(birth: str, compare_date: str, fmt='yyyy-MM-dd') -> int:
+ """
+ 年龄计算
+ :param birth: 生日
+ :param compare_date: 被比较日期
+ :param fmt: 日期格式化 yyyy-MM-dd|yyyyMMdd
+ :return:
+ """
+ fmt_birth = format_date_str(birth, fmt)
+ fmt_compare = format_date_str(compare_date, fmt)
+ birth_m = fmt_birth.replace(year=fmt_compare.year)
+ return fmt_compare.year - fmt_birth.year if fmt_compare > birth_m else fmt_compare.year - fmt_birth.year - 1
+
+
+def age_of_now(birth: str) -> int:
+ """
+ 当前年龄
+ :param birth: 出生日期
+ :return:
+ """
+ return age(birth, current_datetime('yyyy-MM-dd'))
+
+
+def between(dt_1: str, dt_2: str, fmt='yyyy-MM-dd', time_unit='day') -> int:
+ """
+ 计算两个时间差
+ :param dt_1: 时间1 日期或日期时间
+ :param dt_2: 时间2 日期或日期时间
+ :param fmt: 格式化
+ :param time_unit: 时间单位 day: 天,hour: 小时 ,minute:分钟,second:秒
+ :return:
+ """
+ fmt_dt1, fmt_dt2 = format_datetime_str(dt_1, fmt), format_datetime_str(dt_2, fmt)
+ return abs({
+ 'day': (fmt_dt1 - fmt_dt2).days,
+ 'hour': int(fmt_dt1.timestamp() - fmt_dt2.timestamp()) / ONE_HOUR_SECOND,
+ 'second': int(fmt_dt1.timestamp() - fmt_dt2.timestamp())
+ }[time_unit])
+
+
+def time_offset(start_dt: str, offset: int, fmt='yyyy-MM-dd HH:mm:ss', time_unit='day') -> datetime:
+ """
+ 时间偏移计算,例:计算相隔N天后的日期
+ :param start_dt: 开始日期(日期时间)
+ :param fmt: 时间格式化
+ :param offset: 偏移量,支持正负数
+ :param time_unit: 偏移量单位
+ :return:
+ """
+ fmt_dt = format_datetime_str(start_dt, fmt)
+ return {'day': fmt_dt + timedelta(days=offset),
+ 'hour': fmt_dt + timedelta(hours=offset),
+ 'second': fmt_dt + timedelta(seconds=offset)}[time_unit]
+
+
+def is_am(dt: str, fmt='yyyy-MM-dd HH:mm:ss') -> bool:
+ """
+ 是否上午
+ :param dt:
+ :param fmt:
+ :return:
+ """
+ fmt_dt = format_datetime_str(dt, fmt)
+ return fmt_dt.hour <= AM_PM_DIV
+
+
+def is_pm(dt: str, fmt='yyyy-MM-dd HH:mm:ss') -> bool:
+ """
+ 是否下午
+ :param dt:
+ :param fmt:
+ :return:
+ """
+ fmt_dt = format_datetime_str(dt, fmt)
+ return fmt_dt.hour > AM_PM_DIV
+
+
+def next_week(fmt='yyyy-MM-dd HH:mm:ss') -> datetime:
+ """
+ 下周同一天
+ :return:
+ """
+ now = current_datetime(fmt)
+ return time_offset(now, ONE_WEEK_DAYS, fmt)
+
+
+def next_month() -> datetime:
+ """
+ 下个月同一天
+ :return:
+ """
+ return datetime.now().replace(month=current_month() + 1)
diff --git a/futool/core/fu_file.py b/futool/core/fu_file.py
new file mode 100644
index 0000000..9af2076
--- /dev/null
+++ b/futool/core/fu_file.py
@@ -0,0 +1,230 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2022/9/10 10:45
+# @Author : old tom
+# @File : fu_file.py
+# @Project : Futool
+# @Desc : 文件操作
+import shutil
+import os
+import hashlib
+import zipfile
+from pathlib import Path
+from futool.core.fh_file_path import loop_mk_dir, parent_path, exist
+from enum import Enum
+
+
+class FileSuffix(Enum):
+ """
+ 常见文件后缀
+ """
+ XLSX = '.xlsx'
+ XLS = '.xls'
+ DOC = '.doc'
+ DOCX = '.docx'
+ PPT = '.ppt'
+ PPTX = '.pptx'
+ EXE = '.exe'
+ MSI = '.msi'
+ ISO = '.iso'
+ PGP = '.pgp'
+ PNG = '.png'
+ JPG = 'jpg'
+ JPEG = '.jpeg'
+ GIF = '.gif'
+ WAV = '.wav'
+ JAR = '.jar'
+ PY = '.py'
+ BAT = '.bat'
+ DLL = '.dll'
+ ZIP = '.zip'
+ SEVEN_ZIP = '.7z'
+ TAR = '.tar'
+ RAR = '.rar'
+
+
+class FileNotExistError(Exception):
+ def __init__(self, msg=''):
+ Exception.__init__(self, msg)
+
+
+def split_text():
+ pass
+
+
+def split_json():
+ pass
+
+
+def compress(path, z_path, z_name='zip'):
+ """
+ 压缩文件或文件夹
+ :param path:
+ :param z_path: 压缩后路径
+ :param z_name: 压缩格式 zip rar gz
+ :return:
+ """
+ pass
+
+
+def un_compress(path):
+ """
+ 解压
+ :param path:
+ :return:
+ """
+ pass
+
+
+def md5(file_path) -> str:
+ """
+ 文件MD5
+ :param file_path:
+ :return:
+ """
+ if not exist(file_path):
+ raise FileNotExistError('文件不存在')
+ with open(file_path, 'rb') as f:
+ data = f.read()
+ return hashlib.md5(data).hexdigest()
+
+
+def move(src, dst):
+ """
+ 移动文件夹或文件
+ :param src: 源
+ :param dst: 目标
+ :return:
+ """
+ shutil.move(src, dst)
+
+
+def copy(src, dst, override=False):
+ """
+ 复制
+ :param src:
+ :param dst:
+ :param override:
+ :return:
+ """
+ if override and exist(dst):
+ os.remove(dst)
+ shutil.copy(src, dst)
+
+
+def copy_dir(src, dst, override=False) -> str:
+ """
+ 复制文件夹
+ :param src: 源目录
+ :param dst: 目的目录
+ :param override: 是否覆盖
+ :return:
+ """
+ if override and exist(dst):
+ os.remove(dst)
+ return shutil.copytree(src, dst, dirs_exist_ok=override)
+
+
+def copy_file(src, dst, override=False) -> str:
+ """
+ 复制文件
+ :param src: 源文件
+ :param dst: 目的文件或目的目录
+ :param override: 是否覆盖
+ :return:
+ """
+ if override and exist(dst):
+ os.remove(dst)
+ if Path(dst).is_dir():
+ dst = os.path.join(dst, file_full_name(src))
+ return shutil.copyfile(src, dst)
+
+
+def rename(file_path, neo_name) -> Path:
+ """
+ 重命名
+ :param file_path: 文件路径
+ :param neo_name: 新命名
+ :return:
+ """
+ return Path(file_path).rename(parent_path(file_path).joinpath(neo_name))
+
+
+def delete(file_path):
+ """
+ 删除文件
+ :param file_path:
+ :return:
+ """
+ if exist(file_path):
+ os.remove(file_path)
+
+
+def touch(file_path, mode=0o777, cover=True):
+ """
+ 创建文件
+ :param file_path: 文件路径
+ :param mode: 权限
+ :param cover: 是否覆盖
+ :return:
+ """
+ parent = parent_path(file_path)
+ if not parent.exists():
+ loop_mk_dir(str(parent), mode=mode, exist_ok=cover)
+ Path(file_path).touch(mode=mode, exist_ok=cover)
+
+
+def file_name(file_path) -> str:
+ """
+ 获取文件名
+ :param file_path:
+ :return:
+ """
+ full_name = file_full_name(file_path)
+ return full_name.split(sep='.')[0] if (full_name and '.' in full_name) else full_name
+
+
+def file_full_name(file_path) -> str:
+ """
+ 文件全名,带后缀
+ :param file_path:
+ :return:
+ """
+ return Path(file_path).name
+
+
+def suffix(file_path) -> str:
+ """
+ 文件后缀
+ :return:
+ """
+ return Path(file_path).suffix
+
+
+def suffixes(file_path) -> list:
+ """
+ 多后缀,例:xxx.tar.gz
+ :param file_path:
+ :return: [.tar,.gz]
+ """
+ return Path(file_path).suffixes
+
+
+def file_info(file_path):
+ """
+ 返回文件信息
+ :param file_path:
+ :return:
+ st_mode=33206 文件模式:包括文件类型和文件模式位(即权限位)
+ st_ino=281474976714543 与平台有关,但如果不为零,则根据 st_dev 值唯一地标识文件。
+ 通常: 在 Unix 上该值表示索引节点号 (inode number)。 在 Windows 上该值表示 文件索引号 。
+ st_dev=10943705 该文件所在设备的标识符。
+ st_nlink=1 硬链接的数量。
+ st_uid=0 文件所有者的用户 ID。
+ st_gid=0 文件所有者的用户组 ID。
+ st_size=453 文件大小(以字节为单位)
+ st_atime=1662966762 最近的访问时间,以秒为单位
+ st_mtime=1652331424 最近的修改时间,以秒为单位
+ st_ctime=1652331424 在 Windows 上表示创建时间,以秒为单位 在 Unix 上表示最近的元数据更改时间
+ """
+ return Path(file_path).stat()
diff --git a/futool/core/fu_lang.py b/futool/core/fu_lang.py
new file mode 100644
index 0000000..5f235a2
--- /dev/null
+++ b/futool/core/fu_lang.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2022/9/10 10:15
+# @Author : old tom
+# @File : fu_lang.py
+# @Project : Futool
+# @Desc : 字符串相关
diff --git a/futool/core/fu_math.py b/futool/core/fu_math.py
new file mode 100644
index 0000000..69579bc
--- /dev/null
+++ b/futool/core/fu_math.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2022/9/10 10:17
+# @Author : old tom
+# @File : fu_math.py
+# @Project : Futool
+# @Desc : 数学计算
diff --git a/futool/core/fu_parser.py b/futool/core/fu_parser.py
new file mode 100644
index 0000000..ea799c9
--- /dev/null
+++ b/futool/core/fu_parser.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2022/9/10 10:46
+# @Author : old tom
+# @File : fu_parser.py
+# @Project : Futool
+# @Desc : 解析器(CSV,JSON,NB文件)
diff --git a/futool/db/__init__.py b/futool/db/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/futool/db/fh_db.py b/futool/db/fh_db.py
new file mode 100644
index 0000000..53be2e2
--- /dev/null
+++ b/futool/db/fh_db.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2023/4/3 21:46
+# @Author : old tom
+# @File : fh_db.py
+# @Project : futool
+# @Desc : 数据库通用类
+
diff --git a/futool/http/__init__.py b/futool/http/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/futool/http/http_downloader.py b/futool/http/http_downloader.py
new file mode 100644
index 0000000..85d63c6
--- /dev/null
+++ b/futool/http/http_downloader.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2022/8/31 23:34
+# @Author : old tom
+# @File : http_downloader.py
+# @Project : Futool
+# @Desc : 文件下载器
+import time
+
+from futool.http.http_request import head
+from multiprocessing import Pool
+import urllib.request as req
+
+
+class HttpDownloader(object):
+ """
+ HTTP 下载器
+ """
+
+ def __init__(self, pool=None):
+ self.pool = Pool(16) if not pool else pool
+
+ def download(self, url, dst, chunk_size=1000):
+ """
+ 文件下,自动开启多线程
+ :param url: 下载链接
+ :param dst: 保存路径
+ :param chunk_size: 文件块
+ :return:
+ """
+ is_support, content_length = HttpDownloader.is_support_range(url)
+ if is_support:
+ # 每个线程下载字节偏移量
+ offset = self.fork(int(content_length), chunk_size)
+ self.__join(offset, url, dst)
+ else:
+ print('无法获取Content-Length,使用单线程下载')
+ pass
+
+ @staticmethod
+ def is_support_range(url):
+ """
+ 判断是否支持range请求
+ :return:
+ """
+ wrapper = head(url)
+ header = wrapper.header()
+ h_keys = header.keys()
+ if 'Accept-Ranges' in h_keys and 'Content-Length' in h_keys and header['Accept-Ranges'] != 'none':
+ return True, header['Content-Length']
+ else:
+ return False, 0
+
+ @staticmethod
+ def fork(content_length: int, chunk_size):
+ """
+ 拆分线程
+ :param chunk_size: 文件块大小
+ :param content_length:
+ :return:
+ """
+ offset = []
+ if content_length <= chunk_size:
+ offset.append((0, content_length))
+ else:
+ for i in range(content_length // chunk_size):
+ start_offset = chunk_size * i + 1
+ end_offset = start_offset - 1 + chunk_size
+ offset.append((0 if i == 0 else start_offset, end_offset))
+ offset.append((chunk_size * (content_length // chunk_size), content_length))
+ return offset
+
+ def __join(self, offset, url, dst):
+ """
+ 多线程下载
+ :param offset:
+ :param url:
+ :param dst:
+ :return:
+ """
+
+ def download_by_thread(part):
+ _request = req.Request(url=url, headers={
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/104.0.5112.102 Safari/537.36 Edg/104.0.1293.70",
+ 'Range': f'bytes:{part[0]}-{part[1]}'
+ }, method='GET')
+ response = req.urlopen(_request)
+ with open(dst + f'.{time.time_ns()}', 'wb') as f:
+ f.write(response.read())
+
+ self.pool.map(download_by_thread, offset)
+ self.pool.close()
+ self.pool.join()
diff --git a/futool/http/http_request.py b/futool/http/http_request.py
new file mode 100644
index 0000000..4f9dc1a
--- /dev/null
+++ b/futool/http/http_request.py
@@ -0,0 +1,158 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2022/8/29 21:31
+# @Author : old tom
+# @File : http_request.py
+# @Project : Futool
+
+import urllib.request as req
+import urllib.parse
+from futool.http.http_response import ResponseWrapper
+from http import cookiejar
+import json
+import shutil
+import os
+
+DEFAULT_HEADER = {
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/104.0.5112.102 Safari/537.36 Edg/104.0.1293.70",
+ "Accept": "*/*",
+ "Connection": "keep-alive"
+}
+
+DEFAULT_TIMEOUT = 10
+
+
+class HttpRequestError(Exception):
+ def __init__(self, msg=''):
+ Exception.__init__(self, msg)
+
+
+def get(url, param=None, header=None, timeout=DEFAULT_TIMEOUT):
+ """
+ 普通get请求
+ :param url: 请求地址
+ :param param: 参数字典 自动拼接url
+ :param header: 请求头
+ :param timeout: 超时(秒)
+ :return:
+ """
+ return request(url, 'GET', param, header=header, timeout=timeout)
+
+
+def head(url, param=None, header=None, timeout=DEFAULT_TIMEOUT):
+ """
+ 发起head请求
+ :param url:
+ :param param:
+ :param header:
+ :param timeout:
+ :return:
+ """
+ return request(url, 'HEAD', param, header, timeout)
+
+
+def post_form(url, data=None, header=None, timeout=DEFAULT_TIMEOUT):
+ """
+ post 请求提交普通表单
+ :param url:
+ :param data:
+ :param header:
+ :param timeout:
+ :return:
+ """
+ return request(url, 'POST', data=data, header=header, timeout=timeout)
+
+
+def post(url, json_param=None, header=None, timeout=DEFAULT_TIMEOUT):
+ """
+ post 请求提交body 参数
+ :param url:
+ :param json_param:
+ :param header:
+ :param timeout:
+ :return:
+ """
+ return request(url, 'POST', json_param=json_param, header=header, timeout=timeout)
+
+
+def request(url, method, param=None, data=None, json_param=None, header=None, timeout=DEFAULT_TIMEOUT, before_send=None,
+ success_handler=None,
+ error_handler=None):
+ """
+ 基础请求方法
+ :param method: 请求方法 目前支持get post 方法
+ :param url: 请求地址
+ :param param: 请求参数拼接url或表单
+ :param data: 表单参数
+ :param json_param: json body参数
+ :param header: 请求头
+ :param timeout: 超时
+ :param before_send: 发送前处理
+ :param success_handler: 钩子函数(成功处理)
+ :param error_handler: 钩子函数(失败处理)
+ :return:
+ """
+ _request = None
+ if method not in ['GET', 'POST', 'HEAD']:
+ raise HttpRequestError('not support method')
+ if header is None:
+ header = DEFAULT_HEADER
+ opener, cookie = _init_cookie_jar()
+ if before_send:
+ before_send(opener, cookie, header)
+ if method in ['GET', 'HEAD']:
+ if param:
+ param_str = urllib.parse.urlencode(param)
+ url = url + '?' + param_str
+ _request = req.Request(url, headers=header, method=method)
+ if 'POST' == method:
+ if data:
+ # 表单
+ data = urllib.parse.urlencode(data).encode('utf-8')
+ header.update({"Content-Type": "application/x-www-form-urlencoded"})
+ if json_param:
+ # json body
+ data = bytes(json.dumps(json_param), 'utf-8')
+ header.update({"Content-Type": "application/json"})
+ _request = req.Request(url, data=data, headers=header, method=method)
+ wrapper = ResponseWrapper(opener.open(_request, timeout=timeout), cookie)
+ if success_handler and wrapper.is_ok():
+ return success_handler(wrapper)
+ elif error_handler and not wrapper.is_ok():
+ return error_handler(wrapper)
+ return wrapper
+
+
+def _init_cookie_jar():
+ """
+ 初始化cookie容器
+ :return:
+ """
+ cookie = cookiejar.CookieJar()
+ handler = req.HTTPCookieProcessor(cookie)
+ return req.build_opener(handler), cookie
+
+
+def upload_file():
+ # TODO
+ pass
+
+
+def download_file(url, dst_path, overwrite=True, duplicate_handler=None, buffer=16 * 1024):
+ """
+ 文件下载
+ :param url: URL路径
+ :param dst_path: 目标位置
+ :param overwrite: 是否覆盖同名文件
+ :param duplicate_handler: 自定义重复文件处理
+ :param buffer:
+ :return:
+ """
+ if os.path.isfile(dst_path) and overwrite:
+ os.remove(dst_path)
+ if os.path.isfile(dst_path) and not overwrite and duplicate_handler:
+ duplicate_handler(dst_path)
+ b_resp = req.urlopen(url)
+ with open(dst_path, 'wb') as f:
+ shutil.copyfileobj(b_resp, f, buffer)
diff --git a/futool/http/http_response.py b/futool/http/http_response.py
new file mode 100644
index 0000000..df188b5
--- /dev/null
+++ b/futool/http/http_response.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2022/8/29 23:14
+# @Author : old tom
+# @File : http_response.py
+# @Project : Futool
+# @Desc : 响应解析
+
+from http.client import HTTPResponse
+from http.cookiejar import CookieJar
+import json
+
+DEFAULT_ENCODING = 'UTF-8'
+
+# 响应类型编码
+RESPONSE_CONTENT_ENCODING = "Content-Encoding"
+
+# 压缩类型
+COMPRESS_TYPE = ('gzip', 'deflate', 'br')
+
+
+class ResponseWrapper(object):
+
+ def __init__(self, response: HTTPResponse, cookie: CookieJar = None):
+ self.resp = response
+ if cookie and len(cookie) > 0:
+ self.cookie = cookie
+
+ def body(self, encoding=DEFAULT_ENCODING):
+ return self.resp.read().decode(encoding)
+
+ def json_body(self, encoding=DEFAULT_ENCODING):
+ return json.loads(self.body(encoding))
+
+ def status(self):
+ return self.resp.status
+
+ def is_ok(self):
+ st_code = self.resp.status
+ return 200 <= st_code <= 300
+
+ def header(self, name=None):
+ return self.resp.getheader(name) if name else self._parse_header_dict()
+
+ def _parse_header_dict(self):
+ headers = self.resp.getheaders()
+ header_dict = {}
+ if headers:
+ for h in headers:
+ header_dict[h[0]] = h[1]
+ return header_dict
+
+ def is_compress(self):
+ """
+ 是否压缩
+ :return:
+ """
+ return self.compress_type() in COMPRESS_TYPE
+
+ def compress_type(self):
+ """
+ 压缩格式
+ :return:
+ """
+ header = self.header()
+ if RESPONSE_CONTENT_ENCODING in header.keys():
+ res_content_encoding = header[RESPONSE_CONTENT_ENCODING]
+ return res_content_encoding
+
+ def cookies(self):
+ """
+ 获取cookie
+ :return:
+ """
+ ck = {}
+ if self.cookie:
+ for item in self.cookie:
+ ck[item.name] = item.value
+ return ck
diff --git a/futool/net/__init__.py b/futool/net/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/futool/poi/__init__.py b/futool/poi/__init__.py
new file mode 100644
index 0000000..4fc1aa6
--- /dev/null
+++ b/futool/poi/__init__.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2023/4/2 9:07
+# @Author : old tom
+# @File : __init__.py.py
+# @Project : futool
+# @Desc : excel、word操作相关
diff --git a/futool/poi/fu_excel.py b/futool/poi/fu_excel.py
new file mode 100644
index 0000000..6978baa
--- /dev/null
+++ b/futool/poi/fu_excel.py
@@ -0,0 +1,213 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2023/4/2 9:08
+# @Author : old tom
+# @File : fu_excel.py
+# @Project : futool
+# @Desc : 读写excel,需要引入openPyxl
+
+from futool.core import fu_file
+from openpyxl import load_workbook, Workbook
+
+
+class ExcelNotFoundError(Exception):
+ """
+ excel文件不存在
+ """
+
+ def __init__(self, msg=''):
+ Exception.__init__(self, msg)
+
+
+class SheetNotExistError(Exception):
+ """
+ sheet不存在
+ """
+
+ def __init__(self, msg=''):
+ Exception.__init__(self, msg)
+
+
+class ExcelReader(object):
+ """
+ excel读取
+ TODO 存在性能问题需要重构;增加流式读取功能;sheet分片方式存在问题
+ """
+
+ class SheetNotLoadError(Exception):
+ def __init__(self):
+ Exception.__init__(self, 'sheet not load,you need call load_sheet()')
+
+ class OutOfColIndexError(Exception):
+ def __init__(self, msg):
+ Exception.__init__(self, msg)
+
+ def __init__(self, file_path):
+ if not fu_file.exist(file_path):
+ raise ExcelNotFoundError(msg=f'path={file_path}')
+ self.wb = load_workbook(file_path, read_only=True)
+ self.sheetnames = self.wb.sheetnames
+ self.sheet = None
+ self.col_nums = 0
+
+ def load_sheet(self, sheet_name: str):
+ """
+ 指定名称读取sheet
+ """
+ if sheet_name not in self.sheetnames:
+ raise SheetNotExistError(msg=f'{sheet_name} not exists')
+ self.sheet = self.wb[sheet_name]
+ return self.sheet
+
+ def load_sheet_by_index(self, sheet_index: int):
+ """
+ 指定下标读取sheet,从0开始
+ """
+ return self.load_sheet(self.sheetnames[sheet_index])
+
+ def load_sheet_first(self):
+ """
+ 加载第一个sheet
+ :return:
+ """
+ return self.load_sheet_by_index(0)
+
+ def read_row(self, row_index):
+ """
+ 读取某一行
+ """
+ self._check_sheet()
+ element = []
+ for i, row in enumerate(self.sheet):
+ if i == row_index:
+ for cell in row:
+ element.append(cell.value)
+ return element
+
+ def read_range_rows(self, start, end):
+ """
+ 范围读取
+ """
+ self._check_sheet()
+ elements = []
+ for i, row in enumerate(self.sheet):
+ if start <= i <= end:
+ element = []
+ for cell in row:
+ element.append(cell.value)
+ elements.append(tuple(element))
+ return elements
+
+ def read_rows(self, row_index: [] = None):
+ """
+ 指定读取多行
+ :param row_index: 行号,例如:[1,3,5]
+ :return:
+ """
+ self._check_sheet()
+ elements = []
+ for i, row in enumerate(self.sheet):
+ if i in row_index:
+ element = []
+ for cell in row:
+ element.append(cell.value)
+ elements.append(tuple(element))
+ return elements
+
+ def read_first(self):
+ """
+ 读取标题行
+ """
+ return self.read_row(0)
+
+ def read_all(self, skip_head=True):
+ """
+ 读取全部
+ :return:
+ """
+ self._check_sheet()
+ elements = []
+ for i, row in enumerate(self.sheet):
+ if skip_head:
+ if i > 0:
+ element = []
+ for cell in row:
+ element.append(cell.value)
+ elements.append(tuple(element))
+ return elements
+
+ def read_column(self, col_index, skip_head=True):
+ """
+ 按列读取
+ :param col_index: 列下标,从0开始
+ :param skip_head: 跳过第一行
+ :return: list
+ """
+ self._check_sheet()
+ element = []
+ for i, row in enumerate(self.sheet):
+ if skip_head:
+ if i > 0:
+ for j, cell in enumerate(row):
+ if j == col_index:
+ element.append(cell.value)
+ return element
+
+ def read_range_column(self, start, end, skip_head=True):
+ """
+ 范围读取列,下标从0开始
+ :param start: 开始下标
+ :param end: 结束下标
+ :param skip_head: 跳过第一行
+ :return:
+ """
+ self._col_nums()
+ if end >= self.col_nums:
+ raise self.OutOfColIndexError(msg=f'out of column index,max col was {self.col_nums} ')
+ elements = []
+ for i in range(start, end + 1):
+ elements.append(tuple(self.read_column(i, skip_head)))
+ return elements
+
+ def _check_sheet(self):
+ if self.sheet is None:
+ raise self.SheetNotLoadError()
+
+ def _col_nums(self):
+ self.col_nums = len(self.read_first())
+
+
+class SimpleExcelWriter(object):
+ """
+ excel写入
+ """
+
+ class ExcelFileExistsError(Exception):
+ """
+ excel文件已存在
+ """
+
+ def __init__(self, msg=''):
+ Exception.__init__(self, msg)
+
+ def __init__(self, write_path):
+ if fu_file.exist(write_path):
+ raise self.ExcelFileExistsError(f'{write_path} all ready exists')
+ self.write_path = write_path
+ self.wb = Workbook(write_only=True)
+
+ def write(self, head: [], data: [], sheet_name='Sheet1', index=0):
+ """
+ 写excel文件
+ :param index: sheet 下标
+ :param head: 第一行标题
+ :param data: 数据
+ :param sheet_name: sheet名称
+ :return:
+ """
+ ws = self.wb.create_sheet(title=sheet_name, index=index)
+ # 写入列头
+ ws.append(head)
+ for d in data:
+ ws.append(d)
+ self.wb.save(self.write_path)
diff --git a/futool/system/__init__.py b/futool/system/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/futool/system/fu_sys.py b/futool/system/fu_sys.py
new file mode 100644
index 0000000..3563bfc
--- /dev/null
+++ b/futool/system/fu_sys.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Time : 2022/9/9 0:04
+# @Author : old tom
+# @File : fu_sys.py
+# @Project : Futool
+# @Desc : 用于获取当前系统信息,内存及硬盘信息获取请使用psutil
+
+import os
+import platform
+import getpass
+
+
+def is_windows():
+ """
+ 是否windows
+ :return:
+ """
+ return 'Windows' == platform.system()
+
+
+def is_linux():
+ """
+ 是否linux
+ :return:
+ """
+ return 'Linux' == platform.system()
+
+
+def is_unix():
+ """
+ 是否unix
+ :return:
+ """
+ return 'Unix' == platform.system()
+
+
+def os_name():
+ """
+ 操作系统名称
+ :return:
+ """
+ return platform.platform()
+
+
+def host_name():
+ """
+ 获取主机名
+ :return:
+ """
+ return platform.node()
+
+
+def sys_user():
+ """
+ 系统用户
+ :return:
+ """
+ return getpass.getuser()
+
+
+def sys_user_dir():
+ """
+ 当前用户目录
+ :return:
+ """
+ return os.path.expanduser('~')
+
+
+class CpuInfo(object):
+ @staticmethod
+ def cpu_architecture():
+ """
+ CPU架构,AMD64,i386
+ :return:
+ """
+ return platform.machine()
+
+ @staticmethod
+ def cpu_count():
+ """
+ CPU核数
+ :return:
+ """
+ return os.cpu_count()
diff --git a/futool/validator/__init__.py b/futool/validator/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..e7facdd
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+xltpl~=0.18
\ No newline at end of file