cannect 1.0.0__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.
- cannect/__init__.py +30 -0
- cannect/api/__init__.py +0 -0
- cannect/config.py +114 -0
- cannect/core/__init__.py +0 -0
- cannect/core/ascet/__init__.py +3 -0
- cannect/core/ascet/amd.py +698 -0
- cannect/core/ascet/formula.py +33 -0
- cannect/core/ascet/oid.py +29 -0
- cannect/core/ascet/ws.py +154 -0
- cannect/core/can/__init__.py +2 -0
- cannect/core/can/ascet/__init__.py +3 -0
- cannect/core/can/ascet/_db2code.py +344 -0
- cannect/core/can/ascet/_db2elem.py +399 -0
- cannect/core/can/ascet/comdef.py +256 -0
- cannect/core/can/ascet/comrx.py +139 -0
- cannect/core/can/ascet/diag.py +691 -0
- cannect/core/can/db/__init__.py +4 -0
- cannect/core/can/db/reader.py +148 -0
- cannect/core/can/db/schema.py +269 -0
- cannect/core/can/db/specification/__init__.py +0 -0
- cannect/core/can/db/specification/message.py +230 -0
- cannect/core/can/db/specification/styles.py +81 -0
- cannect/core/can/db/specification/wrapper.py +161 -0
- cannect/core/can/db/vcs.py +104 -0
- cannect/core/can/rule.py +229 -0
- cannect/core/can/testcase/__init__.py +0 -0
- cannect/core/can/testcase/unitcase/__init__.py +0 -0
- cannect/core/can/testcase/unitcase/asw2can.py +48 -0
- cannect/core/can/testcase/unitcase/decode.py +63 -0
- cannect/core/can/testcase/unitcase/diagnosis.py +479 -0
- cannect/core/can/testcase/unitcase/encode.py +60 -0
- cannect/core/ir/__init__.py +2 -0
- cannect/core/ir/changehistory.py +310 -0
- cannect/core/ir/delivereables.py +71 -0
- cannect/core/ir/diff.py +97 -0
- cannect/core/ir/ir.py +581 -0
- cannect/core/ir/sdd.py +148 -0
- cannect/core/mdf.py +66 -0
- cannect/core/subversion.py +136 -0
- cannect/core/testcase/__init__.py +3 -0
- cannect/core/testcase/plotter.py +181 -0
- cannect/core/testcase/style.py +981 -0
- cannect/core/testcase/testcase.py +160 -0
- cannect/core/testcase/unitcase.py +227 -0
- cannect/errors.py +20 -0
- cannect/schema/__init__.py +5 -0
- cannect/schema/candb.py +226 -0
- cannect/schema/datadictionary.py +60 -0
- cannect/utils/__init__.py +3 -0
- cannect/utils/excel.py +29 -0
- cannect/utils/logger.py +81 -0
- cannect/utils/ppt.py +236 -0
- cannect/utils/tools.py +207 -0
- cannect-1.0.0.dist-info/METADATA +214 -0
- cannect-1.0.0.dist-info/RECORD +58 -0
- cannect-1.0.0.dist-info/WHEEL +5 -0
- cannect-1.0.0.dist-info/licenses/LICENSE +21 -0
- cannect-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import pprint, os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DataDictionary(dict):
|
|
7
|
+
"""
|
|
8
|
+
데이터 저장 Dictionary
|
|
9
|
+
built-in: dict의 확장으로 저장 요소에 대해 attribute 접근 방식을 허용
|
|
10
|
+
기본 제공 Alias (별칭): dD, dDict
|
|
11
|
+
|
|
12
|
+
사용 예시)
|
|
13
|
+
myData = DataDictionary(name='JEHYEUK', age=34, division='Vehicle Solution Team')
|
|
14
|
+
print(myData.name, myData['name'], myData.name == myData['name'])
|
|
15
|
+
|
|
16
|
+
/* ----------------------------------------------------------------------------------------
|
|
17
|
+
| 결과
|
|
18
|
+
-------------------------------------------------------------------------------------------
|
|
19
|
+
| JEHYEUK JEHYEUK True
|
|
20
|
+
---------------------------------------------------------------------------------------- */
|
|
21
|
+
"""
|
|
22
|
+
def __init__(self, data=None, **kwargs):
|
|
23
|
+
super().__init__()
|
|
24
|
+
|
|
25
|
+
data = data or {}
|
|
26
|
+
data.update(kwargs)
|
|
27
|
+
for key, value in data.items():
|
|
28
|
+
if isinstance(value, dict):
|
|
29
|
+
self[key] = DataDictionary(**value)
|
|
30
|
+
continue
|
|
31
|
+
if isinstance(value, list):
|
|
32
|
+
self[key] = value
|
|
33
|
+
continue
|
|
34
|
+
try:
|
|
35
|
+
if os.path.isdir(value) or os.path.isfile(value):
|
|
36
|
+
value = Path(value)
|
|
37
|
+
except TypeError:
|
|
38
|
+
pass
|
|
39
|
+
self[key] = value
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
def __getattr__(self, attr):
|
|
43
|
+
if attr in self:
|
|
44
|
+
return self[attr]
|
|
45
|
+
return super().__getattribute__(attr)
|
|
46
|
+
|
|
47
|
+
def __setattr__(self, attr, value):
|
|
48
|
+
if isinstance(value, dict):
|
|
49
|
+
self[attr] = DataDictionary(**value)
|
|
50
|
+
else:
|
|
51
|
+
try:
|
|
52
|
+
if os.path.isdir(value) or os.path.isfile(value):
|
|
53
|
+
value = Path(value)
|
|
54
|
+
except TypeError:
|
|
55
|
+
pass
|
|
56
|
+
self[attr] = value
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
def __str__(self) -> str:
|
|
60
|
+
return pprint.pformat(self)
|
cannect/utils/excel.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from pywintypes import com_error
|
|
3
|
+
from typing import Union
|
|
4
|
+
import win32com.client as win32
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ComExcel:
|
|
8
|
+
|
|
9
|
+
app = None
|
|
10
|
+
app_close :bool = False
|
|
11
|
+
app_visible:bool = False
|
|
12
|
+
def __new__(cls, *args, **kwargs):
|
|
13
|
+
try:
|
|
14
|
+
cls.app = win32.GetActiveObject("Excel.Application")
|
|
15
|
+
except com_error:
|
|
16
|
+
cls.app = win32.Dispatch("Excel.Application")
|
|
17
|
+
cls.app_close = True
|
|
18
|
+
cls.app_visible = False
|
|
19
|
+
return super().__new__(cls)
|
|
20
|
+
|
|
21
|
+
def __init__(self, path:Union[str, Path]):
|
|
22
|
+
self.wb = wb = self.app.Workbooks.Open(path)
|
|
23
|
+
self.ws = wb.ActiveSheet
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
# def __del__(self):
|
|
27
|
+
# if self.app_close:
|
|
28
|
+
# self.wb.Close()
|
|
29
|
+
# self.app.Quit()
|
cannect/utils/logger.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from io import StringIO
|
|
2
|
+
import logging, time, os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Logger(logging.Logger):
|
|
6
|
+
|
|
7
|
+
_timer = None
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
def kst(cls, *args):
|
|
11
|
+
return time.localtime(time.mktime(time.gmtime()) + 9 * 3600)
|
|
12
|
+
|
|
13
|
+
def __init__(self, file:str='', clean_record:bool=False):
|
|
14
|
+
|
|
15
|
+
formatter = logging.Formatter(
|
|
16
|
+
fmt=f"%(asctime)s %(message)s",
|
|
17
|
+
datefmt="%Y-%m-%d %H:%M:%S"
|
|
18
|
+
)
|
|
19
|
+
formatter.converter = self.kst
|
|
20
|
+
|
|
21
|
+
console_handler = logging.StreamHandler()
|
|
22
|
+
console_handler.setLevel(logging.INFO)
|
|
23
|
+
console_handler.setFormatter(formatter)
|
|
24
|
+
|
|
25
|
+
self._buffer = StringIO()
|
|
26
|
+
memory = logging.StreamHandler(stream=self._buffer)
|
|
27
|
+
memory.setLevel(logging.INFO)
|
|
28
|
+
memory.setFormatter(formatter)
|
|
29
|
+
|
|
30
|
+
super().__init__(name='pyems', level=logging.DEBUG)
|
|
31
|
+
|
|
32
|
+
self.file = file
|
|
33
|
+
self.propagate = False
|
|
34
|
+
self.addHandler(console_handler)
|
|
35
|
+
self.addHandler(memory)
|
|
36
|
+
if file:
|
|
37
|
+
if os.path.exists(file) and clean_record:
|
|
38
|
+
os.remove(file)
|
|
39
|
+
file_handler = logging.FileHandler(file, encoding="utf-8")
|
|
40
|
+
file_handler.setLevel(logging.DEBUG)
|
|
41
|
+
file_handler.setFormatter(formatter)
|
|
42
|
+
self.addHandler(file_handler)
|
|
43
|
+
self._held = ''
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
def __call__(self, io:str):
|
|
47
|
+
return self.info(io)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def stream(self) -> str:
|
|
51
|
+
return self._buffer.getvalue()
|
|
52
|
+
|
|
53
|
+
def log(self, msg:str='', *args, **kwargs):
|
|
54
|
+
if self._held:
|
|
55
|
+
msg = self._held + msg
|
|
56
|
+
super().info(msg, *args, **kwargs)
|
|
57
|
+
self._held = ''
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
def read(self, file:str=''):
|
|
61
|
+
if not file:
|
|
62
|
+
file = self.file
|
|
63
|
+
with open(file, 'r', encoding="utf-8") as f:
|
|
64
|
+
return f.read()
|
|
65
|
+
|
|
66
|
+
def hold(self, msg:str):
|
|
67
|
+
self._held += msg
|
|
68
|
+
|
|
69
|
+
def run(self, context:str=''):
|
|
70
|
+
if context:
|
|
71
|
+
self.info(context)
|
|
72
|
+
self._timer = time.perf_counter()
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
def end(self, context:str=''):
|
|
76
|
+
try:
|
|
77
|
+
context = f'{context} {time.perf_counter() - self._timer:.2f}s'
|
|
78
|
+
self.info(context)
|
|
79
|
+
except (AttributeError, TypeError, Exception):
|
|
80
|
+
raise RuntimeError('Logger is not started. Please call .run() method first.')
|
|
81
|
+
return
|
cannect/utils/ppt.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
from pywintypes import com_error
|
|
2
|
+
from typing import Generator, List
|
|
3
|
+
import win32com.client as win32
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# def add_OLE(
|
|
8
|
+
# ppt_win32,
|
|
9
|
+
# obj:str,
|
|
10
|
+
# slide:int=1,
|
|
11
|
+
# left:int=475,
|
|
12
|
+
# top:int=192,
|
|
13
|
+
# width:int=100,
|
|
14
|
+
# height:int=100,
|
|
15
|
+
# close:bool=True,
|
|
16
|
+
# ):
|
|
17
|
+
# slide = ppt_win32.Slides.Item(slide)
|
|
18
|
+
#
|
|
19
|
+
# # 아이콘으로 표시 여부 / 링크 여부
|
|
20
|
+
# display_as_icon = True
|
|
21
|
+
# link = False # True면 링크, False면 프레젠테이션에 임베드
|
|
22
|
+
#
|
|
23
|
+
# # AddOLEObject (파일 확장자에 따라 ProgID 자동 판단; 명시 가능)
|
|
24
|
+
# shape = slide.Shapes.AddOLEObject(
|
|
25
|
+
# Left=left, Top=top, Width=width, Height=height,
|
|
26
|
+
# ClassName="", # 예: "Excel.Sheet.12" (Office 2007+)
|
|
27
|
+
# FileName=obj,
|
|
28
|
+
# DisplayAsIcon=display_as_icon,
|
|
29
|
+
# IconFileName=" ", # 아이콘 파일 지정 가능(생략 시 기본)
|
|
30
|
+
# IconIndex=0,
|
|
31
|
+
# # IconLabel=os.path.basename(obj),
|
|
32
|
+
# IconLabel='',
|
|
33
|
+
# Link=link
|
|
34
|
+
# )
|
|
35
|
+
#
|
|
36
|
+
# if close:
|
|
37
|
+
# ppt_win32.SaveAs(ppt_path)
|
|
38
|
+
# ppt_win32.close()
|
|
39
|
+
# return
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class PptRW:
|
|
43
|
+
|
|
44
|
+
app = None
|
|
45
|
+
app_close:bool = False
|
|
46
|
+
def __new__(cls, *args, **kwargs):
|
|
47
|
+
try:
|
|
48
|
+
cls.app = win32.GetActiveObject("PowerPoint.Application")
|
|
49
|
+
except com_error:
|
|
50
|
+
cls.app = win32.Dispatch("PowerPoint.Application")
|
|
51
|
+
cls.app_close = True
|
|
52
|
+
cls.app.Visible = True
|
|
53
|
+
return super().__new__(cls)
|
|
54
|
+
|
|
55
|
+
def __init__(self, path:str, logger=None):
|
|
56
|
+
if logger is not None:
|
|
57
|
+
logger.log(f'[WRITE PPT ON "{os.path.basename(path)}"]')
|
|
58
|
+
if self.app.Presentations.Count > 0:
|
|
59
|
+
for n in range(1, self.app.Presentations.Count + 1):
|
|
60
|
+
_ppt = self.app.Presentations.Item(n)
|
|
61
|
+
if os.path.join(_ppt.Path, _ppt.Name) == path:
|
|
62
|
+
self.ppt = _ppt
|
|
63
|
+
return
|
|
64
|
+
self.ppt = self.app.Presentations.Open(path)
|
|
65
|
+
self.log = logger
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
def __iter__(self) -> Generator[win32.CDispatch, None, None]:
|
|
69
|
+
for i in range(1, self.ppt.Slides.Count + 1):
|
|
70
|
+
yield self.ppt.Slides.Item(i)
|
|
71
|
+
|
|
72
|
+
def _get_table(self, n_slide:int, n_table:int):
|
|
73
|
+
slide = self.ppt.Slides.Item(n_slide)
|
|
74
|
+
n = 0
|
|
75
|
+
for shape in slide.Shapes:
|
|
76
|
+
if shape.HasTable:
|
|
77
|
+
n += 1
|
|
78
|
+
if not n == n_table:
|
|
79
|
+
continue
|
|
80
|
+
return shape.Table
|
|
81
|
+
raise com_error(f'Table Not Found')
|
|
82
|
+
|
|
83
|
+
def get_slide_n(self, title:str) -> List:
|
|
84
|
+
ns = []
|
|
85
|
+
for n, slide in enumerate(self, start=1):
|
|
86
|
+
if slide.Shapes.Count == 0:
|
|
87
|
+
continue
|
|
88
|
+
for m in range(1, slide.Shapes.Count + 1):
|
|
89
|
+
shape = slide.Shapes.Item(1)
|
|
90
|
+
if not shape.HasTextFrame:
|
|
91
|
+
continue
|
|
92
|
+
text = shape.TextFrame.TextRange.Text
|
|
93
|
+
if (title.lower() in text.lower()) and (not n in ns):
|
|
94
|
+
ns.append(n)
|
|
95
|
+
return ns
|
|
96
|
+
|
|
97
|
+
def set_shape(self, n_slide:int, n_shape:int, width:float=None, height:float=None, left=None, top=None):
|
|
98
|
+
if width:
|
|
99
|
+
self.ppt.Slides.Item(n_slide).Shapes(n_shape).Width = width
|
|
100
|
+
if height:
|
|
101
|
+
self.ppt.Slides.Item(n_slide).Shapes(n_shape).Height = height
|
|
102
|
+
if left:
|
|
103
|
+
self.ppt.Slides.Item(n_slide).Shapes(n_shape).Left = left
|
|
104
|
+
if top:
|
|
105
|
+
self.ppt.Slides.Item(n_slide).Shapes(n_shape).Top = top
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
def set_table_height(self, n_slide:int, n_table:int, row:int, height:float):
|
|
109
|
+
self._get_table(n_slide, n_table).Rows(row).Height = height
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
def set_table_text_align(
|
|
113
|
+
self,
|
|
114
|
+
n_slide:int,
|
|
115
|
+
n_table:int,
|
|
116
|
+
cell:tuple,
|
|
117
|
+
horizontal:int=1,
|
|
118
|
+
vertical:int=1
|
|
119
|
+
):
|
|
120
|
+
cell = self._get_table(n_slide, n_table).Cell(*cell)
|
|
121
|
+
# text_frame =
|
|
122
|
+
# text_range = text_frame.TextRange
|
|
123
|
+
|
|
124
|
+
cell.Shape.TextFrame.TextRange.ParagraphFormat.Alignment = horizontal
|
|
125
|
+
cell.Shape.TextFrame.VerticalAnchor = vertical
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
def set_table_font(
|
|
129
|
+
self,
|
|
130
|
+
n_slide:int,
|
|
131
|
+
n_table:int,
|
|
132
|
+
cell:tuple,
|
|
133
|
+
name:str=None,
|
|
134
|
+
size:int=None,
|
|
135
|
+
bold:bool=None,
|
|
136
|
+
color:str=None,
|
|
137
|
+
):
|
|
138
|
+
font = self._get_table(n_slide, n_table).Cell(*cell).Shape.TextFrame.TextRange.Font
|
|
139
|
+
if name is not None:
|
|
140
|
+
font.Name = name
|
|
141
|
+
if size is not None:
|
|
142
|
+
font.Size = size
|
|
143
|
+
if bold is not None:
|
|
144
|
+
font.Bold = bold
|
|
145
|
+
if color is not None:
|
|
146
|
+
font.Color.RGB = color
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
def set_text(
|
|
150
|
+
self,
|
|
151
|
+
n_slide:int,
|
|
152
|
+
n_shape:int,
|
|
153
|
+
text:str,
|
|
154
|
+
pos:str='new',
|
|
155
|
+
):
|
|
156
|
+
shape = self.ppt.Slides.Item(n_slide).Shapes(n_shape)
|
|
157
|
+
if shape.HasTextFrame:
|
|
158
|
+
if pos.lower() == 'after':
|
|
159
|
+
shape.TextFrame.TextRange.InsertAfter(text) # Error
|
|
160
|
+
elif pos.lower() == 'before':
|
|
161
|
+
shape.TextFrame.TextRange.InsertBefore(text)
|
|
162
|
+
else:
|
|
163
|
+
shape.TextFrame.TextRange.Text = text
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
def set_text_font(
|
|
167
|
+
self,
|
|
168
|
+
n_slide:int,
|
|
169
|
+
n_shape:int,
|
|
170
|
+
name:str=None,
|
|
171
|
+
size:int=None,
|
|
172
|
+
bold:bool=None,
|
|
173
|
+
color:str=None,
|
|
174
|
+
):
|
|
175
|
+
font = self.ppt.Slides.Item(n_slide).Shapes(n_shape).TextFrame.TextRange.Font
|
|
176
|
+
if name is not None:
|
|
177
|
+
font.Name = name
|
|
178
|
+
if size is not None:
|
|
179
|
+
font.Size = size
|
|
180
|
+
if bold is not None:
|
|
181
|
+
font.Bold = bold
|
|
182
|
+
if color is not None:
|
|
183
|
+
font.Color.RGB = color
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
def set_text_in_table(
|
|
187
|
+
self,
|
|
188
|
+
n_slide:int,
|
|
189
|
+
n_table:int,
|
|
190
|
+
cell:tuple,
|
|
191
|
+
text:str,
|
|
192
|
+
pos:str='new'
|
|
193
|
+
):
|
|
194
|
+
table = self._get_table(n_slide, n_table)
|
|
195
|
+
if pos.lower() == 'after':
|
|
196
|
+
table.Cell(cell[0], cell[1]).Shape.TextFrame.TextRange.InsertAfter(text)
|
|
197
|
+
elif pos.lower() == 'before':
|
|
198
|
+
table.Cell(cell[0], cell[1]).Shape.TextFrame.TextRange.InsertBefore(text)
|
|
199
|
+
else:
|
|
200
|
+
table.Cell(cell[0], cell[1]).Shape.TextFrame.TextRange.Text = text
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
def replace_text_in_table(
|
|
204
|
+
self,
|
|
205
|
+
n_slide:int,
|
|
206
|
+
n_table:int,
|
|
207
|
+
cell:tuple,
|
|
208
|
+
prev:str,
|
|
209
|
+
post:str
|
|
210
|
+
):
|
|
211
|
+
self._get_table(n_slide, n_table) \
|
|
212
|
+
.Cell(cell[0], cell[1]) \
|
|
213
|
+
.Shape \
|
|
214
|
+
.TextFrame \
|
|
215
|
+
.TextRange \
|
|
216
|
+
.Replace(prev, post)
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
def close(self):
|
|
220
|
+
self.ppt.Save()
|
|
221
|
+
self.ppt.Close()
|
|
222
|
+
if self.app_close:
|
|
223
|
+
self.app.Quit()
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
if __name__ == "__main__":
|
|
230
|
+
# ppt = PptRW(r"D:\Archive\00_프로젝트\2017 통신개발-\2025\DS1229 CR10785896 CNG PIO\0000_변경내역서 양식.pptx")
|
|
231
|
+
# ppt.set_text(1, 1, "Hello World", insert=False)
|
|
232
|
+
# ppt.set_text_in_table(2, (2, 1), "Testing", insert=False)
|
|
233
|
+
# ppt.close()
|
|
234
|
+
|
|
235
|
+
print(win32.constants)
|
|
236
|
+
print(win32.constants.msoAnchorTop)
|
cannect/utils/tools.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import AnyStr, Callable, Iterable, List, Union
|
|
3
|
+
from xml.etree.ElementTree import Element, ElementTree
|
|
4
|
+
from xml.dom import minidom
|
|
5
|
+
import os, zipfile, shutil, io, re
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def unzip(src: Union[str, Path], to: Union[str, Path] = "") -> bool:
|
|
9
|
+
"""
|
|
10
|
+
압축(.zip) 해제
|
|
11
|
+
:param src: 압축파일 경로
|
|
12
|
+
:param to : [optional] 압축파일을 풀 경로
|
|
13
|
+
:return:
|
|
14
|
+
"""
|
|
15
|
+
if not to:
|
|
16
|
+
to = os.path.dirname(src)
|
|
17
|
+
else:
|
|
18
|
+
os.makedirs(to, exist_ok=True)
|
|
19
|
+
|
|
20
|
+
src = str(src)
|
|
21
|
+
if not os.path.isfile(src):
|
|
22
|
+
raise KeyError(f"src: {src}는 경로가 포함된 파일(Full-Directory)이어야 합니다.")
|
|
23
|
+
if src.endswith('.zip'):
|
|
24
|
+
zip_obj = zipfile.ZipFile(src)
|
|
25
|
+
zip_obj.extractall(to)
|
|
26
|
+
# elif src.endswith('.7z'):
|
|
27
|
+
# with py7zr.SevenZipFile(src, 'r') as arc:
|
|
28
|
+
# arc.extractall(path=to)
|
|
29
|
+
else:
|
|
30
|
+
# raise KeyError(f"src: {src}는 .zip 또는 .7z 압축 파일만 입력할 수 있습니다.")
|
|
31
|
+
raise KeyError(f"src: {src}는 .zip 압축 파일만 입력할 수 있습니다.")
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
def zip(path:Union[str, Path]):
|
|
35
|
+
name = os.path.basename(path)
|
|
36
|
+
shutil.make_archive(name, "zip", root_dir=path)
|
|
37
|
+
shutil.move(f'{name}.zip', path)
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
def copy_to(file:Union[str, Path], dst:Union[str, Path]) -> str:
|
|
41
|
+
shutil.copy(file, dst)
|
|
42
|
+
# if '.' in os.path.basename(file):
|
|
43
|
+
# shutil.copy(file, dst)
|
|
44
|
+
# else:
|
|
45
|
+
# shutil.move(file, dst)
|
|
46
|
+
return os.path.join(dst, os.path.basename(file))
|
|
47
|
+
|
|
48
|
+
def find_file(root:Union[str, Path], filename:Union[str, Path]) -> Union[str, List[str]]:
|
|
49
|
+
"""
|
|
50
|
+
@filename: 확장자까지 포함한 단일 파일 이름
|
|
51
|
+
"""
|
|
52
|
+
found = []
|
|
53
|
+
for _root, _dir, _files in os.walk(root):
|
|
54
|
+
for _file in _files:
|
|
55
|
+
if _file == filename:
|
|
56
|
+
found.append(os.path.join(_root, _file))
|
|
57
|
+
if not found:
|
|
58
|
+
return ""
|
|
59
|
+
if len(found) == 1:
|
|
60
|
+
return found[0]
|
|
61
|
+
return found
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def clear(path: str, leave_path: bool = True):
|
|
65
|
+
"""
|
|
66
|
+
지정한 경로의 파일 및 하위 디렉토리를 모두 삭제
|
|
67
|
+
|
|
68
|
+
:param path: 삭제 대상이 될 폴더 경로
|
|
69
|
+
:param leave_path: True면 폴더를 남기고 내용만 삭제, False면 폴더 자체도 삭제
|
|
70
|
+
"""
|
|
71
|
+
if not os.path.exists(path):
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
if leave_path:
|
|
76
|
+
for item in os.listdir(path):
|
|
77
|
+
item_path = os.path.join(path, item)
|
|
78
|
+
if os.path.isfile(item_path) or os.path.islink(item_path):
|
|
79
|
+
os.unlink(item_path)
|
|
80
|
+
elif os.path.isdir(item_path):
|
|
81
|
+
shutil.rmtree(item_path)
|
|
82
|
+
else:
|
|
83
|
+
shutil.rmtree(path)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(f"Error occurs while clearing directory: {e}")
|
|
86
|
+
|
|
87
|
+
def path_abbreviate(path: Union[str, Path]) -> str:
|
|
88
|
+
sep = os.path.sep
|
|
89
|
+
split = str(path).split(sep)
|
|
90
|
+
return f"{sep.join(split[:2])}{sep} ... {sep}{sep.join(split[-3:])}"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class xml:
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def to_str(cls, xml: Union[Element, ElementTree], xml_declaration: bool = False) -> str:
|
|
97
|
+
"""
|
|
98
|
+
xml 요소(태그) 및 그 하위 요소를 소스 문자열로 변환
|
|
99
|
+
|
|
100
|
+
:param xml:
|
|
101
|
+
:param xml_declaration:
|
|
102
|
+
|
|
103
|
+
:return:
|
|
104
|
+
"""
|
|
105
|
+
if isinstance(xml, Element):
|
|
106
|
+
xml = ElementTree(xml)
|
|
107
|
+
|
|
108
|
+
stream = io.StringIO()
|
|
109
|
+
xml.write(
|
|
110
|
+
file_or_filename=stream,
|
|
111
|
+
encoding='unicode',
|
|
112
|
+
xml_declaration=False,
|
|
113
|
+
method='xml',
|
|
114
|
+
)
|
|
115
|
+
dom = f'{minidom.parseString(stream.getvalue()).toprettyxml()}' \
|
|
116
|
+
.replace('<?xml version="1.0" ?>', '<?xml version="1.0" encoding="UTF-8"?>') \
|
|
117
|
+
.replace("<CodeBlock/>", "<CodeBlock></CodeBlock>") \
|
|
118
|
+
.replace("<Comment/>", "<Comment></Comment>") \
|
|
119
|
+
.replace("ns0:", "") \
|
|
120
|
+
.replace('xmlns:ns0="http://www.w3.org/2000/09/xmldsig#" ', '') \
|
|
121
|
+
.replace('<Signature>', '<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">')
|
|
122
|
+
# if not xml.getroot().tag == 'Specifications':
|
|
123
|
+
dom = "\n".join([l for l in dom.split("\n") if "<" in l or ';' in l or '=' in l or not l.startswith("\t")]) # dom = '\n'.join([line for line in dom.split('\n') if line.strip()])
|
|
124
|
+
if not xml_declaration:
|
|
125
|
+
dom = dom.replace('<?xml version="1.0" encoding="UTF-8"?>\n', '')
|
|
126
|
+
return dom
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def to_dict(cls, xml:Union[Element, ElementTree], target:str='ascet', depth:str='recursive') -> dict:
|
|
130
|
+
"""
|
|
131
|
+
xml 요소(태그) 및 그 하위 요소의 text 및 attribute를 dictionary로 변환
|
|
132
|
+
|
|
133
|
+
:param xml:
|
|
134
|
+
:param target:
|
|
135
|
+
:param depth:
|
|
136
|
+
:return:
|
|
137
|
+
"""
|
|
138
|
+
if isinstance(xml, Element):
|
|
139
|
+
xml = ElementTree(xml)
|
|
140
|
+
|
|
141
|
+
if not depth == 'recursive':
|
|
142
|
+
return xml.getroot().attrib
|
|
143
|
+
|
|
144
|
+
attr = {}
|
|
145
|
+
for elem in xml.iter():
|
|
146
|
+
copy = elem.attrib.copy()
|
|
147
|
+
if target == "ascet":
|
|
148
|
+
if elem.tag == 'PhysicalInterval' and 'min' in copy:
|
|
149
|
+
copy['physMin'] = copy['min']
|
|
150
|
+
copy['physMax'] = copy['max']
|
|
151
|
+
del copy['min'], copy['max']
|
|
152
|
+
if elem.tag == 'ImplementationInterval' and 'min' in copy:
|
|
153
|
+
copy['implMin'] = copy['min']
|
|
154
|
+
copy['implMax'] = copy['max']
|
|
155
|
+
del copy['min'], copy['max']
|
|
156
|
+
|
|
157
|
+
if (elem.tag == 'Comment' and elem.text is not None) or elem.tag == 'CodeBlock':
|
|
158
|
+
attr[elem.tag.lower()] = elem.text
|
|
159
|
+
|
|
160
|
+
attr.update(copy)
|
|
161
|
+
return attr
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class KeywordSearch:
|
|
166
|
+
"""
|
|
167
|
+
Dataset에 대한 키워드 검색
|
|
168
|
+
"""
|
|
169
|
+
logger:Callable = print
|
|
170
|
+
def __init__(self, *samples):
|
|
171
|
+
self._samples = list(samples)
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
def __getitem__(self, item:str)-> str:
|
|
175
|
+
result = self.search(self._samples, item)
|
|
176
|
+
if not result:
|
|
177
|
+
self.logger(f"NOT FOUND: '{item}' IN THE MODEL")
|
|
178
|
+
return result
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def search(cls, samples: Iterable[str], keyword: str) -> Union[str, List[str]]:
|
|
182
|
+
"""
|
|
183
|
+
간단한 와일드카드 검색 함수.
|
|
184
|
+
- '*' 는 0글자 이상 임의의 문자열과 매칭됩니다.
|
|
185
|
+
- 대소문자를 구분하지 않습니다.
|
|
186
|
+
- 결과가 1개이면 str을, 그 외(0개 또는 2개 이상)면 List[str]를 반환합니다.
|
|
187
|
+
"""
|
|
188
|
+
samples = list(samples)
|
|
189
|
+
if keyword is None:
|
|
190
|
+
return ""
|
|
191
|
+
|
|
192
|
+
kw = keyword.strip()
|
|
193
|
+
regex_fragments = []
|
|
194
|
+
for ch in kw:
|
|
195
|
+
if ch == '*':
|
|
196
|
+
regex_fragments.append(".*")
|
|
197
|
+
else:
|
|
198
|
+
regex_fragments.append(re.escape(ch))
|
|
199
|
+
regex_body = "".join(regex_fragments)
|
|
200
|
+
pattern = re.compile(f"^{regex_body}$", flags=re.IGNORECASE)
|
|
201
|
+
matches = [s for s in samples if pattern.match(s)]
|
|
202
|
+
if not matches:
|
|
203
|
+
return ""
|
|
204
|
+
elif len(matches) == 1:
|
|
205
|
+
return matches[0]
|
|
206
|
+
else:
|
|
207
|
+
return matches
|