drawlib 0.1.6__tar.gz → 0.1.7__tar.gz
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.
Potentially problematic release.
This version of drawlib might be problematic. Click here for more details.
- {drawlib-0.1.6 → drawlib-0.1.7}/PKG-INFO +1 -1
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/__init__.py +1 -1
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/apis.py +0 -3
- drawlib-0.1.7/drawlib/v0_1/private/command.py +385 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core/theme.py +83 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core/theme_officials.py +21 -10
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/settings.py +1 -1
- {drawlib-0.1.6 → drawlib-0.1.7}/pyproject.toml +1 -3
- drawlib-0.1.6/drawlib/v0_1/private/command.py +0 -204
- drawlib-0.1.6/drawlib/v0_1/private/tools.py +0 -161
- {drawlib-0.1.6 → drawlib-0.1.7}/LICENSE.txt +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/README.md +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/.pylintrc +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/__main__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/apis.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/assets/__init__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/assets/v0_1/__init__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/assets/v0_1/fonticons/__init__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/assets/v0_1/fonts/__init__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/__init__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/__main__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/__init__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core/__init__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core/colors.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core/dimage.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core/fonts.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core/model.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core/theme_styles.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core/util.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core_canvas/__init__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core_canvas/base.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core_canvas/canvas.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core_canvas/image.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core_canvas/line.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core_canvas/original_arrow.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core_canvas/original_polygon.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core_canvas/patches.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/core_canvas/text.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/download.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/dutil.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/icons/__init__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/icons/phosphor.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/icons/util.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/logging.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/smartarts/__init__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/smartarts/bubblespeech.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/smartarts/cycle.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/smartarts/dsart.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/smartarts/groups.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/smartarts/pyramid.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/smartarts/sourcecode.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/smartarts/table.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/smartarts/tree.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/util.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/validators/__init__.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/validators/args.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/validators/color.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/validators/coordinate.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/validators/image.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/validators/line.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/validators/shape.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/validators/smartarts.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/validators/style.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/validators/text.py +0 -0
- {drawlib-0.1.6 → drawlib-0.1.7}/drawlib/v0_1/private/validators/types.py +0 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# Copyright (c) 2024 Yuichi Ito (yuichi@yuichi.com)
|
|
2
|
+
#
|
|
3
|
+
# This software is licensed under the Apache License, Version 2.0.
|
|
4
|
+
# For more information, please visit: https://github.com/yuichi110/drawlib
|
|
5
|
+
#
|
|
6
|
+
# This software is provided "as is", without warranty of any kind,
|
|
7
|
+
# express or implied, including but not limited to the warranties of
|
|
8
|
+
# merchantability, fitness for a particular purpose and noninfringement.
|
|
9
|
+
|
|
10
|
+
"""Handle drawlib command
|
|
11
|
+
|
|
12
|
+
Entry point ``__main__`` calls this module's function ``call()``.
|
|
13
|
+
After that class ``DrawlibArgParser`` parse options.
|
|
14
|
+
Then operate appropriate actions.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
import argparse
|
|
21
|
+
from typing import Optional, List, Final
|
|
22
|
+
import importlib.util
|
|
23
|
+
import traceback
|
|
24
|
+
|
|
25
|
+
import drawlib
|
|
26
|
+
import drawlib.v0_1
|
|
27
|
+
from drawlib.v0_1.private.logging import logger
|
|
28
|
+
from drawlib.v0_1.private.settings import dsettings
|
|
29
|
+
from drawlib.v0_1.private.util import (
|
|
30
|
+
error_handler,
|
|
31
|
+
get_script_relative_path,
|
|
32
|
+
)
|
|
33
|
+
from drawlib.v0_1.private.core_canvas.canvas import clear
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def call() -> None:
|
|
37
|
+
"""drawlib command handling function.
|
|
38
|
+
|
|
39
|
+
This method is called from each API's ``__main__`` module.
|
|
40
|
+
If the API is latest, called from root package's ``__main__`` module too.
|
|
41
|
+
|
|
42
|
+
Abstract procedure of this function.
|
|
43
|
+
|
|
44
|
+
1. Crate DrawlibArgParser instance.
|
|
45
|
+
2. Parse command line options
|
|
46
|
+
3. If options requests version info, show it and quit.
|
|
47
|
+
4. Apply options such as logging level
|
|
48
|
+
5. get target files and directories which drawlib run.
|
|
49
|
+
6. call ``tools.run()`` for each files, directories
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
None
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
argparser = DrawlibArgParser()
|
|
57
|
+
argparser.parse()
|
|
58
|
+
argparser.show_version_then_exit0_if_requested()
|
|
59
|
+
argparser.apply_options()
|
|
60
|
+
paths = argparser.get_positional_args()
|
|
61
|
+
|
|
62
|
+
if len(paths) == 0:
|
|
63
|
+
logger.critical("no input files and directories")
|
|
64
|
+
logger.critical('check options with "drawlib --help"')
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
for path in paths:
|
|
68
|
+
if not os.path.isfile(path) and not os.path.isdir(path):
|
|
69
|
+
msg = f'ignore arg "{path}" since it is not a file/dir path'
|
|
70
|
+
logger.warning(msg)
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
abspath = os.path.abspath(path)
|
|
74
|
+
realpath = os.path.realpath(abspath)
|
|
75
|
+
executer = DrawlibExecuter()
|
|
76
|
+
executer.execute(realpath)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class DrawlibArgParser:
|
|
80
|
+
"""drawlib ArgParser Class"""
|
|
81
|
+
|
|
82
|
+
def __init__(self) -> None:
|
|
83
|
+
"""Initializa DrawlibArgParser
|
|
84
|
+
|
|
85
|
+
In this method, create ``ArgumentParser`` instance and
|
|
86
|
+
define command line options.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
None
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
parser = argparse.ArgumentParser(
|
|
94
|
+
description="Ilustration as code by python",
|
|
95
|
+
)
|
|
96
|
+
parser.add_argument(
|
|
97
|
+
"file_or_directory",
|
|
98
|
+
nargs="...",
|
|
99
|
+
help="Target python file or directory which contains python codes",
|
|
100
|
+
)
|
|
101
|
+
parser.add_argument(
|
|
102
|
+
"-v",
|
|
103
|
+
"--version",
|
|
104
|
+
action="store_true",
|
|
105
|
+
help="Show version",
|
|
106
|
+
)
|
|
107
|
+
parser.add_argument(
|
|
108
|
+
"--quiet",
|
|
109
|
+
action="store_true",
|
|
110
|
+
help="Enable quiet logging. show only error messages",
|
|
111
|
+
)
|
|
112
|
+
parser.add_argument(
|
|
113
|
+
"--verbose",
|
|
114
|
+
action="store_true",
|
|
115
|
+
help="Enable verbose logging.",
|
|
116
|
+
)
|
|
117
|
+
parser.add_argument(
|
|
118
|
+
"--developer",
|
|
119
|
+
action="store_true",
|
|
120
|
+
help=("Enable verbose logging. " "Disable error handling for library users."),
|
|
121
|
+
)
|
|
122
|
+
self._parser = parser
|
|
123
|
+
self._positional_args: Optional[List[str]] = None
|
|
124
|
+
self._name_args: Optional[argparse.Namespace] = None
|
|
125
|
+
|
|
126
|
+
def parse(self) -> None:
|
|
127
|
+
"""Parse command line options.
|
|
128
|
+
|
|
129
|
+
Parse command line options.
|
|
130
|
+
This method must be called before calling get methods.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
None
|
|
134
|
+
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
self._name_args, _ = self._parser.parse_known_args()
|
|
138
|
+
args = self._parser.parse_args()
|
|
139
|
+
self._positional_args = args.file_or_directory
|
|
140
|
+
|
|
141
|
+
def get_positional_args(self) -> List[str]:
|
|
142
|
+
"""Get positional args. which are files and directories.
|
|
143
|
+
|
|
144
|
+
drawlib command requires files and directories as positional args.
|
|
145
|
+
They are the target of ``tool.run()`` function.
|
|
146
|
+
It means specified file or files inside directories
|
|
147
|
+
are called from python.
|
|
148
|
+
If those python files are drawlib's one, generate images.
|
|
149
|
+
|
|
150
|
+
This method must be called after ``parse()`` method is called.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
List[str]: Positional args (files and directories)
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
if self._positional_args is None:
|
|
158
|
+
logger.critical("DrawlibArgParser must be parsed before getting optional args")
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
|
|
161
|
+
return self._positional_args
|
|
162
|
+
|
|
163
|
+
def show_version_then_exit0_if_requested(self) -> None:
|
|
164
|
+
"""show version if option ``-v/`` or ``--version`` exists. And then quit.
|
|
165
|
+
|
|
166
|
+
Check whether option has ``-v`` or ``--version``.
|
|
167
|
+
If yes, show version. And then, quit with ``sys.exit(0)``.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
None
|
|
171
|
+
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
if self._name_args is None:
|
|
175
|
+
logger.critical("DrawlibArgParser must be parsed before apply options")
|
|
176
|
+
sys.exit(1)
|
|
177
|
+
|
|
178
|
+
if self._name_args.version:
|
|
179
|
+
logger.critical(f"software={drawlib.VERSION}")
|
|
180
|
+
logger.critical(f"api={drawlib.v0_1.VERSION}")
|
|
181
|
+
sys.exit(0)
|
|
182
|
+
|
|
183
|
+
def apply_options(self) -> None:
|
|
184
|
+
"""Check named args and apply appropriate configs.
|
|
185
|
+
|
|
186
|
+
Check these named argses. And do appropriate actions.
|
|
187
|
+
|
|
188
|
+
* --quiet: make logging mode quiet
|
|
189
|
+
* --debug: make logging mode verbose
|
|
190
|
+
* --devdebug: make logging mode developer
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
None
|
|
194
|
+
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
if self._name_args is None:
|
|
198
|
+
logger.critical("DrawlibArgParser must be parsed before apply options")
|
|
199
|
+
sys.exit(1)
|
|
200
|
+
|
|
201
|
+
if self._name_args.quiet and (self._name_args.verbose or self._name_args.developer):
|
|
202
|
+
raise ValueError("option --quiet can't use with option --debug and --devdebug")
|
|
203
|
+
|
|
204
|
+
if self._name_args.quiet:
|
|
205
|
+
dsettings.set_logging_mode("quiet")
|
|
206
|
+
|
|
207
|
+
if self._name_args.verbose:
|
|
208
|
+
dsettings.set_logging_mode("verbose")
|
|
209
|
+
|
|
210
|
+
if self._name_args.developer:
|
|
211
|
+
dsettings.set_logging_mode("developer")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class DrawlibExecuter:
|
|
215
|
+
|
|
216
|
+
def __init__(self) -> None:
|
|
217
|
+
self._topdir_path: str = ""
|
|
218
|
+
|
|
219
|
+
@error_handler
|
|
220
|
+
def execute(self, file_or_directory: str, auto_clear: bool = True) -> None:
|
|
221
|
+
"""Run specified python file or python files in specified directory
|
|
222
|
+
|
|
223
|
+
Call python file via OS path.
|
|
224
|
+
When you specify python file, only run it.
|
|
225
|
+
When you specify directory, run all python files recursively inside it.
|
|
226
|
+
Each python files are called only 1 time.
|
|
227
|
+
2nd time call is skipped.
|
|
228
|
+
|
|
229
|
+
When file1.py calls ``run("file2.py")`` and file2.py calls
|
|
230
|
+
``run("file3.py")``, loading file3.py first.
|
|
231
|
+
This recursive load situation can be checked on console.
|
|
232
|
+
Please don't make circular loading situation.
|
|
233
|
+
|
|
234
|
+
Note:
|
|
235
|
+
We recommend creating ``.drawlib`` directory and put all python files which are loaded.
|
|
236
|
+
Each drawing python file need to declare only ``run(<path_to_.drawlib>)``.
|
|
237
|
+
|
|
238
|
+
Strongly recommend ``auto_clear=True`` when specifying directory.
|
|
239
|
+
If user code doesn't call ``clear()``,
|
|
240
|
+
canvas state is shared between python files.
|
|
241
|
+
It means 2nd draw can contain 1st draw results.
|
|
242
|
+
Default value of ``auto_clear`` is True.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
file_or_directory: run target. relative to user script path.
|
|
246
|
+
auto_clear(optional): call ``clear()`` method between running each python files.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
None
|
|
250
|
+
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
path = get_script_relative_path(file_or_directory)
|
|
254
|
+
if not os.path.exists(path):
|
|
255
|
+
raise ValueError(f'"{path}" does not exist')
|
|
256
|
+
|
|
257
|
+
self._add_topdir_to_syspath(path)
|
|
258
|
+
|
|
259
|
+
logger.info("Execute python files")
|
|
260
|
+
if os.path.isfile(path):
|
|
261
|
+
if not path.endswith(".py"):
|
|
262
|
+
raise ValueError(f'Unable to run "{path}"')
|
|
263
|
+
self._exec_module(path, auto_clear)
|
|
264
|
+
else:
|
|
265
|
+
file_paths = self._get_python_files(path)
|
|
266
|
+
for file_path in file_paths:
|
|
267
|
+
self._exec_module(file_path, auto_clear)
|
|
268
|
+
|
|
269
|
+
def _add_topdir_to_syspath(self, path: str):
|
|
270
|
+
if os.path.isfile(path):
|
|
271
|
+
topdir = os.path.dirname(path)
|
|
272
|
+
else:
|
|
273
|
+
init_path = os.path.join(path, "__init__.py")
|
|
274
|
+
if not os.path.exists(init_path):
|
|
275
|
+
logger.critical(f'Target directory "{path}" does not have __init__.py. Abort.')
|
|
276
|
+
sys.exit(1)
|
|
277
|
+
topdir = path
|
|
278
|
+
|
|
279
|
+
package_dir = ""
|
|
280
|
+
while os.path.exists(os.path.join(topdir, "__init__.py")):
|
|
281
|
+
package_dir = topdir
|
|
282
|
+
topdir = os.path.dirname(topdir)
|
|
283
|
+
|
|
284
|
+
self._topdir_path = topdir
|
|
285
|
+
logger.info(f'Detect package root "{package_dir}".')
|
|
286
|
+
if topdir not in sys.path:
|
|
287
|
+
sys.path.append(topdir)
|
|
288
|
+
logger.info(f' - Add parent directory of package root "{topdir}" to Python Path.')
|
|
289
|
+
else:
|
|
290
|
+
logger.info(f' - Parent of package directory "{topdir}" is already in Python Path.')
|
|
291
|
+
logger.info("")
|
|
292
|
+
|
|
293
|
+
def _get_python_files(self, directory: str) -> List[str]:
|
|
294
|
+
python_files: List[str] = []
|
|
295
|
+
for root, _, files in os.walk(directory):
|
|
296
|
+
for file in files:
|
|
297
|
+
if file.endswith(".py"):
|
|
298
|
+
file_path = os.path.join(root, file)
|
|
299
|
+
python_files.append(file_path)
|
|
300
|
+
|
|
301
|
+
sorted_files = sorted(python_files, key=lambda path: path.count(os.sep))
|
|
302
|
+
return sorted_files
|
|
303
|
+
|
|
304
|
+
def _exec_module(self, file_path: str, auto_clear: bool) -> None:
|
|
305
|
+
"""write docstring later"""
|
|
306
|
+
|
|
307
|
+
words = self._get_package_words(file_path)
|
|
308
|
+
self._load_parent_modules(words)
|
|
309
|
+
|
|
310
|
+
if self._is_module_loaded(file_path):
|
|
311
|
+
logger.info(f" - {file_path}")
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
# load module
|
|
315
|
+
name = ".".join(words).replace(".py", "")
|
|
316
|
+
mspec = importlib.util.spec_from_file_location(
|
|
317
|
+
name=name,
|
|
318
|
+
location=file_path,
|
|
319
|
+
)
|
|
320
|
+
if mspec is None:
|
|
321
|
+
# need to investigate what situation make this
|
|
322
|
+
logger.info(f" - {file_path} : skipped with unknown reason.")
|
|
323
|
+
return
|
|
324
|
+
module = importlib.util.module_from_spec(mspec)
|
|
325
|
+
|
|
326
|
+
# execute and cache
|
|
327
|
+
try:
|
|
328
|
+
if auto_clear:
|
|
329
|
+
clear()
|
|
330
|
+
logger.info(f" - {file_path}")
|
|
331
|
+
mspec.loader.exec_module(module) # type: ignore[union-attr]
|
|
332
|
+
sys.modules[name] = module
|
|
333
|
+
|
|
334
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
335
|
+
# Please don't raise again.
|
|
336
|
+
# Error handler can't detect exact error location of imported module
|
|
337
|
+
file, line, _, _ = traceback.extract_tb(e.__traceback__)[-1]
|
|
338
|
+
logger.critical(f'{type(e).__name__} at file:"{file}", line:"{line}"')
|
|
339
|
+
logger.critical(str(e))
|
|
340
|
+
logger.debug("")
|
|
341
|
+
logger.debug(traceback.format_exc())
|
|
342
|
+
sys.exit(1)
|
|
343
|
+
|
|
344
|
+
def _is_module_loaded(self, module_path: str) -> bool:
|
|
345
|
+
# Normalize the module path to ensure consistent comparison
|
|
346
|
+
module_path = os.path.abspath(module_path)
|
|
347
|
+
|
|
348
|
+
for module_name, module in sys.modules.items():
|
|
349
|
+
if module is None:
|
|
350
|
+
continue
|
|
351
|
+
if not hasattr(module, "__file__"):
|
|
352
|
+
continue
|
|
353
|
+
if module.__file__ is None:
|
|
354
|
+
continue
|
|
355
|
+
|
|
356
|
+
loaded_module_path = os.path.abspath(module.__file__)
|
|
357
|
+
if loaded_module_path == module_path:
|
|
358
|
+
return True
|
|
359
|
+
|
|
360
|
+
return False
|
|
361
|
+
|
|
362
|
+
def _get_package_words(self, file_path):
|
|
363
|
+
last_path = file_path.replace(self._topdir_path, "")
|
|
364
|
+
words = last_path.split(os.sep)
|
|
365
|
+
if words[0] == "":
|
|
366
|
+
words = words[1:]
|
|
367
|
+
return words
|
|
368
|
+
|
|
369
|
+
def _load_parent_modules(self, words: List[str]):
|
|
370
|
+
for i in range(1, len(words)):
|
|
371
|
+
name = ".".join(words[:i])
|
|
372
|
+
if name in sys.modules:
|
|
373
|
+
continue
|
|
374
|
+
|
|
375
|
+
location = os.path.join(self._topdir_path, os.sep.join(words[:i]), "__init__.py")
|
|
376
|
+
if not os.path.exists(location):
|
|
377
|
+
raise ValueError(f'"{location}" does not exist. Please create it first.')
|
|
378
|
+
|
|
379
|
+
mspec = importlib.util.spec_from_file_location(
|
|
380
|
+
name=name,
|
|
381
|
+
location=location,
|
|
382
|
+
)
|
|
383
|
+
module = importlib.util.module_from_spec(mspec)
|
|
384
|
+
mspec.loader.exec_module(module)
|
|
385
|
+
sys.modules[name] = module
|
|
@@ -93,6 +93,7 @@ class Theme:
|
|
|
93
93
|
|
|
94
94
|
@dataclasses.dataclass
|
|
95
95
|
class ThemeStyle:
|
|
96
|
+
# mandatory for default
|
|
96
97
|
backgroundcolor: Union[Tuple[int, int, int], Tuple[int, int, int, float], None] = None
|
|
97
98
|
sourcecodefont: Optional[FontSourceCode] = None
|
|
98
99
|
iconstyle: Optional[IconStyle] = None
|
|
@@ -103,8 +104,14 @@ class Theme:
|
|
|
103
104
|
shapetextstyle: Optional[ShapeTextStyle] = None
|
|
104
105
|
textstyle: Optional[TextStyle] = None
|
|
105
106
|
|
|
107
|
+
# optional for default
|
|
108
|
+
arcstyle: Optional[ShapeStyle] = None
|
|
109
|
+
arttextstyle: Optional[ShapeTextStyle] = None
|
|
110
|
+
|
|
106
111
|
@error_handler
|
|
107
112
|
def __init__(self):
|
|
113
|
+
self._style_names: List[str] = []
|
|
114
|
+
|
|
108
115
|
self.backgroundcolors = BackgroundColors()
|
|
109
116
|
self.sourcecodefonts = SourceCodeFonts()
|
|
110
117
|
self.iconstyles = IconStyles()
|
|
@@ -174,6 +181,8 @@ class Theme:
|
|
|
174
181
|
|
|
175
182
|
"""
|
|
176
183
|
|
|
184
|
+
self._style_names: List[str] = []
|
|
185
|
+
|
|
177
186
|
self.backgroundcolors = BackgroundColors()
|
|
178
187
|
self.sourcecodefonts = SourceCodeFonts()
|
|
179
188
|
self.iconstyles = IconStyles()
|
|
@@ -291,6 +300,8 @@ class Theme:
|
|
|
291
300
|
# initialize
|
|
292
301
|
self._initialize()
|
|
293
302
|
|
|
303
|
+
self._style_names.append("")
|
|
304
|
+
|
|
294
305
|
# set default background color
|
|
295
306
|
_check_color(
|
|
296
307
|
default_style.backgroundcolor,
|
|
@@ -335,6 +346,8 @@ class Theme:
|
|
|
335
346
|
|
|
336
347
|
# named styles
|
|
337
348
|
for name, styles in named_styles:
|
|
349
|
+
self._style_names.append(name)
|
|
350
|
+
|
|
338
351
|
if styles.backgroundcolor is not None:
|
|
339
352
|
_check_color(
|
|
340
353
|
styles.backgroundcolor,
|
|
@@ -383,6 +396,76 @@ class Theme:
|
|
|
383
396
|
raise ValueError(f"name={name}, ThemeStyle.textstyle type must be TextStyle.")
|
|
384
397
|
self.textstyles.set(styles.textstyle, name)
|
|
385
398
|
|
|
399
|
+
@error_handler
|
|
400
|
+
def print_style_table(self) -> None:
|
|
401
|
+
print(self._get_style_table())
|
|
402
|
+
|
|
403
|
+
def _get_style_table(self) -> str:
|
|
404
|
+
lines: List[str] = []
|
|
405
|
+
|
|
406
|
+
col1: List[str] = [
|
|
407
|
+
"",
|
|
408
|
+
"IconStyle",
|
|
409
|
+
"ImageStyle",
|
|
410
|
+
"LineStyle",
|
|
411
|
+
"LineArrowStyle",
|
|
412
|
+
"ShapeStyle",
|
|
413
|
+
"ShapeTextStyle",
|
|
414
|
+
"TextStyle",
|
|
415
|
+
]
|
|
416
|
+
|
|
417
|
+
row_objects = [
|
|
418
|
+
self.iconstyles,
|
|
419
|
+
self.imagestyles,
|
|
420
|
+
self.linestyles,
|
|
421
|
+
self.linearrowstyles,
|
|
422
|
+
self.shapestyles,
|
|
423
|
+
self.shapetextstyles,
|
|
424
|
+
self.textstyles,
|
|
425
|
+
]
|
|
426
|
+
|
|
427
|
+
# create columns
|
|
428
|
+
cols: List[List[str]] = [col1]
|
|
429
|
+
for style_name in self._style_names:
|
|
430
|
+
col: List[str] = [style_name]
|
|
431
|
+
for row_object in row_objects:
|
|
432
|
+
if row_object.has(style_name):
|
|
433
|
+
col.append("x")
|
|
434
|
+
else:
|
|
435
|
+
col.append("")
|
|
436
|
+
cols.append(col)
|
|
437
|
+
|
|
438
|
+
# modify text length
|
|
439
|
+
cols2 = []
|
|
440
|
+
for col in cols:
|
|
441
|
+
max_length = max(len(s) for s in col)
|
|
442
|
+
padded_strings = [s.ljust(max_length) for s in col]
|
|
443
|
+
cols2.append(padded_strings)
|
|
444
|
+
|
|
445
|
+
def draw_border():
|
|
446
|
+
text = "+"
|
|
447
|
+
for col in cols2:
|
|
448
|
+
t = "-" * (len(col[0]) + 2)
|
|
449
|
+
text += f"{t}+"
|
|
450
|
+
lines.append(text)
|
|
451
|
+
|
|
452
|
+
for i in range(len(col1)):
|
|
453
|
+
if i == 0:
|
|
454
|
+
draw_border()
|
|
455
|
+
|
|
456
|
+
text = "|"
|
|
457
|
+
for col in cols2:
|
|
458
|
+
text += f" {col[i]} |"
|
|
459
|
+
lines.append(text)
|
|
460
|
+
|
|
461
|
+
if i == 0:
|
|
462
|
+
draw_border()
|
|
463
|
+
|
|
464
|
+
if i == len(col1) - 1:
|
|
465
|
+
draw_border()
|
|
466
|
+
|
|
467
|
+
return "\n".join(lines).strip()
|
|
468
|
+
|
|
386
469
|
|
|
387
470
|
def _check_color(color, error_message: str):
|
|
388
471
|
|
|
@@ -407,30 +407,41 @@ def _add_special_name_styles(
|
|
|
407
407
|
]
|
|
408
408
|
]:
|
|
409
409
|
new_named_styles = deepcopy(named_styles)
|
|
410
|
-
default_shapetextstyle = default_styles[5]
|
|
411
410
|
|
|
412
411
|
# add white ShapeTextStyle
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
412
|
+
has_named_style_white = False
|
|
413
|
+
for named_style in new_named_styles:
|
|
414
|
+
name = named_style[0]
|
|
415
|
+
if name == "white":
|
|
416
|
+
has_named_style_white = True
|
|
417
|
+
|
|
418
|
+
if not has_named_style_white:
|
|
419
|
+
default_shapetextstyle = default_styles[5]
|
|
420
|
+
if not isinstance(default_shapetextstyle, ShapeTextStyle):
|
|
421
|
+
raise ValueError()
|
|
422
|
+
white_shapetextstyle = default_shapetextstyle.copy()
|
|
423
|
+
white_shapetextstyle.color = Colors.White
|
|
424
|
+
new_named_styles.append(("white", [white_shapetextstyle]))
|
|
418
425
|
|
|
419
426
|
# add IconStyle and LineArrowStyle with new names
|
|
420
|
-
|
|
427
|
+
inserted = 0
|
|
428
|
+
for i, (name, styles) in enumerate(named_styles, start=1):
|
|
421
429
|
icon, image, line, linearrow, shape, shapetext, text = styles
|
|
422
430
|
if isinstance(icon, IconStyle) and isinstance(shape, ShapeStyle):
|
|
423
431
|
icon_lcolor = icon.copy()
|
|
424
432
|
icon_lcolor.color = shape.lcolor
|
|
425
433
|
icon_fcolor = icon.copy()
|
|
426
434
|
icon_fcolor.color = shape.fcolor
|
|
427
|
-
new_named_styles.
|
|
428
|
-
|
|
435
|
+
new_named_styles.insert(i + inserted, (f"lc-{name}", [icon_lcolor]))
|
|
436
|
+
inserted += 1
|
|
437
|
+
new_named_styles.insert(i + inserted, (f"fc-{name}", [icon_fcolor]))
|
|
438
|
+
inserted += 1
|
|
429
439
|
else:
|
|
430
440
|
raise ValueError()
|
|
431
441
|
|
|
432
442
|
if isinstance(linearrow, LineArrowStyle):
|
|
433
|
-
new_named_styles.
|
|
443
|
+
new_named_styles.insert(i + inserted, (f"la-{name}", [linearrow]))
|
|
444
|
+
inserted += 1
|
|
434
445
|
else:
|
|
435
446
|
raise ValueError()
|
|
436
447
|
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "drawlib"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.7"
|
|
8
8
|
description = "Python drawing library. Illustration as Code."
|
|
9
9
|
homepage = "https://www.drawlib.com"
|
|
10
10
|
repository = "https://github.com/yuichi110/drawlib"
|
|
@@ -29,6 +29,4 @@ radon = "^6.0.1"
|
|
|
29
29
|
bandit = "^1.7.8"
|
|
30
30
|
pytest-cov = "^5.0.0"
|
|
31
31
|
pydocstyle = "^6.3.0"
|
|
32
|
-
sphinx = "^7.2.6"
|
|
33
|
-
sphinx-rtd-theme = "^2.0.0"
|
|
34
32
|
toml = "^0.10.2"
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Yuichi Ito (yuichi@yuichi.com)
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the Apache License, Version 2.0.
|
|
4
|
-
# For more information, please visit: https://github.com/yuichi110/drawlib
|
|
5
|
-
#
|
|
6
|
-
# This software is provided "as is", without warranty of any kind,
|
|
7
|
-
# express or implied, including but not limited to the warranties of
|
|
8
|
-
# merchantability, fitness for a particular purpose and noninfringement.
|
|
9
|
-
|
|
10
|
-
"""Handle drawlib command
|
|
11
|
-
|
|
12
|
-
Entry point ``__main__`` calls this module's function ``call()``.
|
|
13
|
-
After that class ``DrawlibArgParser`` parse options.
|
|
14
|
-
Then operate appropriate actions.
|
|
15
|
-
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
import os
|
|
19
|
-
import sys
|
|
20
|
-
import argparse
|
|
21
|
-
from typing import Optional, List
|
|
22
|
-
import drawlib
|
|
23
|
-
import drawlib.v0_1
|
|
24
|
-
from drawlib.v0_1.private.logging import logger
|
|
25
|
-
from drawlib.v0_1.private.settings import dsettings
|
|
26
|
-
from drawlib.v0_1.private.tools import dexec
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def call() -> None:
|
|
30
|
-
"""drawlib command handling function.
|
|
31
|
-
|
|
32
|
-
This method is called from each API's ``__main__`` module.
|
|
33
|
-
If the API is latest, called from root package's ``__main__`` module too.
|
|
34
|
-
|
|
35
|
-
Abstract procedure of this function.
|
|
36
|
-
|
|
37
|
-
1. Crate DrawlibArgParser instance.
|
|
38
|
-
2. Parse command line options
|
|
39
|
-
3. If options requests version info, show it and quit.
|
|
40
|
-
4. Apply options such as logging level
|
|
41
|
-
5. get target files and directories which drawlib run.
|
|
42
|
-
6. call ``tools.run()`` for each files, directories
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
None
|
|
46
|
-
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
argparser = DrawlibArgParser()
|
|
50
|
-
argparser.parse()
|
|
51
|
-
argparser.show_version_then_exit0_if_requested()
|
|
52
|
-
argparser.apply_options()
|
|
53
|
-
paths = argparser.get_positional_args()
|
|
54
|
-
|
|
55
|
-
if len(paths) == 0:
|
|
56
|
-
logger.critical("no input files and directories")
|
|
57
|
-
logger.critical('check options with "drawlib --help"')
|
|
58
|
-
sys.exit(1)
|
|
59
|
-
|
|
60
|
-
for path in paths:
|
|
61
|
-
if not os.path.isfile(path) and not os.path.isdir(path):
|
|
62
|
-
msg = f'ignore arg "{path}" since it is not a file/dir path'
|
|
63
|
-
logger.warning(msg)
|
|
64
|
-
continue
|
|
65
|
-
|
|
66
|
-
abspath = os.path.abspath(path)
|
|
67
|
-
realpath = os.path.realpath(abspath)
|
|
68
|
-
logger.info(realpath)
|
|
69
|
-
dexec(realpath)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
class DrawlibArgParser:
|
|
73
|
-
"""drawlib ArgParser Class"""
|
|
74
|
-
|
|
75
|
-
def __init__(self) -> None:
|
|
76
|
-
"""Initializa DrawlibArgParser
|
|
77
|
-
|
|
78
|
-
In this method, create ``ArgumentParser`` instance and
|
|
79
|
-
define command line options.
|
|
80
|
-
|
|
81
|
-
Returns:
|
|
82
|
-
None
|
|
83
|
-
|
|
84
|
-
"""
|
|
85
|
-
|
|
86
|
-
parser = argparse.ArgumentParser(
|
|
87
|
-
description="Ilustration as code by python",
|
|
88
|
-
)
|
|
89
|
-
parser.add_argument(
|
|
90
|
-
"file_or_directory",
|
|
91
|
-
nargs="...",
|
|
92
|
-
help="Target python file or directory which contains python codes",
|
|
93
|
-
)
|
|
94
|
-
parser.add_argument(
|
|
95
|
-
"-v",
|
|
96
|
-
"--version",
|
|
97
|
-
action="store_true",
|
|
98
|
-
help="Show version",
|
|
99
|
-
)
|
|
100
|
-
parser.add_argument(
|
|
101
|
-
"--quiet",
|
|
102
|
-
action="store_true",
|
|
103
|
-
help="Enable quiet logging. show only error messages",
|
|
104
|
-
)
|
|
105
|
-
parser.add_argument(
|
|
106
|
-
"--verbose",
|
|
107
|
-
action="store_true",
|
|
108
|
-
help="Enable verbose logging.",
|
|
109
|
-
)
|
|
110
|
-
parser.add_argument(
|
|
111
|
-
"--developer",
|
|
112
|
-
action="store_true",
|
|
113
|
-
help=("Enable verbose logging. " "Disable error handling for library users."),
|
|
114
|
-
)
|
|
115
|
-
self._parser = parser
|
|
116
|
-
self._positional_args: Optional[List[str]] = None
|
|
117
|
-
self._name_args: Optional[argparse.Namespace] = None
|
|
118
|
-
|
|
119
|
-
def parse(self) -> None:
|
|
120
|
-
"""Parse command line options.
|
|
121
|
-
|
|
122
|
-
Parse command line options.
|
|
123
|
-
This method must be called before calling get methods.
|
|
124
|
-
|
|
125
|
-
Returns:
|
|
126
|
-
None
|
|
127
|
-
|
|
128
|
-
"""
|
|
129
|
-
|
|
130
|
-
self._name_args, _ = self._parser.parse_known_args()
|
|
131
|
-
args = self._parser.parse_args()
|
|
132
|
-
self._positional_args = args.file_or_directory
|
|
133
|
-
|
|
134
|
-
def get_positional_args(self) -> List[str]:
|
|
135
|
-
"""Get positional args. which are files and directories.
|
|
136
|
-
|
|
137
|
-
drawlib command requires files and directories as positional args.
|
|
138
|
-
They are the target of ``tool.run()`` function.
|
|
139
|
-
It means specified file or files inside directories
|
|
140
|
-
are called from python.
|
|
141
|
-
If those python files are drawlib's one, generate images.
|
|
142
|
-
|
|
143
|
-
This method must be called after ``parse()`` method is called.
|
|
144
|
-
|
|
145
|
-
Returns:
|
|
146
|
-
List[str]: Positional args (files and directories)
|
|
147
|
-
|
|
148
|
-
"""
|
|
149
|
-
|
|
150
|
-
if self._positional_args is None:
|
|
151
|
-
logger.critical("DrawlibArgParser must be parsed before getting optional args")
|
|
152
|
-
sys.exit(1)
|
|
153
|
-
|
|
154
|
-
return self._positional_args
|
|
155
|
-
|
|
156
|
-
def show_version_then_exit0_if_requested(self) -> None:
|
|
157
|
-
"""show version if option ``-v/`` or ``--version`` exists. And then quit.
|
|
158
|
-
|
|
159
|
-
Check whether option has ``-v`` or ``--version``.
|
|
160
|
-
If yes, show version. And then, quit with ``sys.exit(0)``.
|
|
161
|
-
|
|
162
|
-
Returns:
|
|
163
|
-
None
|
|
164
|
-
|
|
165
|
-
"""
|
|
166
|
-
|
|
167
|
-
if self._name_args is None:
|
|
168
|
-
logger.critical("DrawlibArgParser must be parsed before apply options")
|
|
169
|
-
sys.exit(1)
|
|
170
|
-
|
|
171
|
-
if self._name_args.version:
|
|
172
|
-
logger.critical(f"software={drawlib.VERSION}")
|
|
173
|
-
logger.critical(f"api={drawlib.v0_1.VERSION}")
|
|
174
|
-
sys.exit(0)
|
|
175
|
-
|
|
176
|
-
def apply_options(self) -> None:
|
|
177
|
-
"""Check named args and apply appropriate configs.
|
|
178
|
-
|
|
179
|
-
Check these named argses. And do appropriate actions.
|
|
180
|
-
|
|
181
|
-
* --quiet: make logging mode quiet
|
|
182
|
-
* --debug: make logging mode verbose
|
|
183
|
-
* --devdebug: make logging mode developer
|
|
184
|
-
|
|
185
|
-
Returns:
|
|
186
|
-
None
|
|
187
|
-
|
|
188
|
-
"""
|
|
189
|
-
|
|
190
|
-
if self._name_args is None:
|
|
191
|
-
logger.critical("DrawlibArgParser must be parsed before apply options")
|
|
192
|
-
sys.exit(1)
|
|
193
|
-
|
|
194
|
-
if self._name_args.quiet and (self._name_args.verbose or self._name_args.developer):
|
|
195
|
-
raise ValueError("option --quiet can't use with option --debug and --devdebug")
|
|
196
|
-
|
|
197
|
-
if self._name_args.quiet:
|
|
198
|
-
dsettings.set_logging_mode("quiet")
|
|
199
|
-
|
|
200
|
-
if self._name_args.verbose:
|
|
201
|
-
dsettings.set_logging_mode("verbose")
|
|
202
|
-
|
|
203
|
-
if self._name_args.developer:
|
|
204
|
-
dsettings.set_logging_mode("developer")
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Yuichi Ito (yuichi@yuichi.com)
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the Apache License, Version 2.0.
|
|
4
|
-
# For more information, please visit: https://github.com/yuichi110/drawlib
|
|
5
|
-
#
|
|
6
|
-
# This software is provided "as is", without warranty of any kind,
|
|
7
|
-
# express or implied, including but not limited to the warranties of
|
|
8
|
-
# merchantability, fitness for a particular purpose and noninfringement.
|
|
9
|
-
|
|
10
|
-
"""This module defines tool functions
|
|
11
|
-
|
|
12
|
-
Note:
|
|
13
|
-
This module's concept is very similar to util module.
|
|
14
|
-
However, please don't merge them.
|
|
15
|
-
|
|
16
|
-
This module is created for avoiding circular import between ``canvas`` and ``util``.
|
|
17
|
-
Function ``run()`` depends on ``canvas`` and ``canvas_singleton``.
|
|
18
|
-
And ``canvas`` and core modules depends on ``util``.
|
|
19
|
-
If you move function ``run()`` to ``util``, it makes import trouble.
|
|
20
|
-
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
# pylint: disable=logging-fstring-interpolation
|
|
24
|
-
# pylint: disable=global-statement
|
|
25
|
-
|
|
26
|
-
import os
|
|
27
|
-
from typing import Set, List, Final
|
|
28
|
-
import importlib.util
|
|
29
|
-
import traceback
|
|
30
|
-
import sys
|
|
31
|
-
|
|
32
|
-
from drawlib.v0_1.private.logging import logger
|
|
33
|
-
from drawlib.v0_1.private.util import (
|
|
34
|
-
error_handler,
|
|
35
|
-
get_script_relative_path,
|
|
36
|
-
)
|
|
37
|
-
from drawlib.v0_1.private.core_canvas.canvas import clear
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class DrawlibExecuter:
|
|
41
|
-
_INDENT_SIZE: Final[int] = 4
|
|
42
|
-
|
|
43
|
-
def __init__(self) -> None:
|
|
44
|
-
self._executed_files: Set[str] = set()
|
|
45
|
-
self._load_stack: int = 0
|
|
46
|
-
|
|
47
|
-
@error_handler
|
|
48
|
-
def dexec(self, file_or_directory: str, auto_clear: bool = True) -> None:
|
|
49
|
-
"""Run specified python file or python files in specified directory
|
|
50
|
-
|
|
51
|
-
Call python file via OS path.
|
|
52
|
-
When you specify python file, only run it.
|
|
53
|
-
When you specify directory, run all python files recursively inside it.
|
|
54
|
-
Each python files are called only 1 time.
|
|
55
|
-
2nd time call is skipped.
|
|
56
|
-
|
|
57
|
-
When file1.py calls ``run("file2.py")`` and file2.py calls
|
|
58
|
-
``run("file3.py")``, loading file3.py first.
|
|
59
|
-
This recursive load situation can be checked on console.
|
|
60
|
-
Please don't make circular loading situation.
|
|
61
|
-
|
|
62
|
-
Note:
|
|
63
|
-
We recommend creating ``.drawlib`` directory and put all python files which are loaded.
|
|
64
|
-
Each drawing python file need to declare only ``run(<path_to_.drawlib>)``.
|
|
65
|
-
|
|
66
|
-
Strongly recommend ``auto_clear=True`` when specifying directory.
|
|
67
|
-
If user code doesn't call ``clear()``,
|
|
68
|
-
canvas state is shared between python files.
|
|
69
|
-
It means 2nd draw can contain 1st draw results.
|
|
70
|
-
Default value of ``auto_clear`` is True.
|
|
71
|
-
|
|
72
|
-
Args:
|
|
73
|
-
file_or_directory: run target. relative to user script path.
|
|
74
|
-
auto_clear(optional): call ``clear()`` method between running each python files.
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
None
|
|
78
|
-
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
path = get_script_relative_path(file_or_directory)
|
|
82
|
-
if not os.path.exists(path):
|
|
83
|
-
raise ValueError(f'"{path}" does not exist')
|
|
84
|
-
|
|
85
|
-
if os.path.isfile(path):
|
|
86
|
-
if not path.endswith(".py"):
|
|
87
|
-
raise ValueError(f'Unable to run "{path}"')
|
|
88
|
-
self._load_file(path, auto_clear)
|
|
89
|
-
else:
|
|
90
|
-
self._load_directory(path, auto_clear)
|
|
91
|
-
|
|
92
|
-
def _load_file(self, path: str, auto_clear: bool):
|
|
93
|
-
self._load_stack += 1
|
|
94
|
-
indent = " " * (self._load_stack - 1) * self._INDENT_SIZE
|
|
95
|
-
logger.info(f"{indent}---")
|
|
96
|
-
logger.info(f"{indent}Execute file")
|
|
97
|
-
self._exec_module(path, auto_clear)
|
|
98
|
-
logger.info(f"{indent}End")
|
|
99
|
-
logger.info(f"{indent}---")
|
|
100
|
-
self._load_stack -= 1
|
|
101
|
-
|
|
102
|
-
def _load_directory(self, path: str, auto_clear: bool):
|
|
103
|
-
self._load_stack += 1
|
|
104
|
-
indent = " " * (self._load_stack - 1) * self._INDENT_SIZE
|
|
105
|
-
logger.info(f"{indent}---")
|
|
106
|
-
logger.info(f"{indent}Execute files under: {path}")
|
|
107
|
-
file_paths = self._get_python_files(path)
|
|
108
|
-
for file_path in file_paths:
|
|
109
|
-
self._exec_module(file_path, auto_clear)
|
|
110
|
-
logger.info(f"{indent}End")
|
|
111
|
-
logger.info(f"{indent}---")
|
|
112
|
-
self._load_stack -= 1
|
|
113
|
-
|
|
114
|
-
def _get_python_files(self, directory: str) -> List[str]:
|
|
115
|
-
python_files: List[str] = []
|
|
116
|
-
for root, _, files in os.walk(directory):
|
|
117
|
-
for file in files:
|
|
118
|
-
if file.endswith(".py"):
|
|
119
|
-
file_path = os.path.join(root, file)
|
|
120
|
-
python_files.append(file_path)
|
|
121
|
-
|
|
122
|
-
return sorted(python_files, key=lambda path: path.count(os.sep))
|
|
123
|
-
|
|
124
|
-
def _exec_module(self, file_path: str, auto_clear: bool) -> None:
|
|
125
|
-
"""write docstring later"""
|
|
126
|
-
|
|
127
|
-
# run only 1 time.
|
|
128
|
-
# don't move .add() to bottom. It makes loop
|
|
129
|
-
if file_path in self._executed_files:
|
|
130
|
-
return
|
|
131
|
-
self._executed_files.add(file_path)
|
|
132
|
-
|
|
133
|
-
mspec = importlib.util.spec_from_file_location(
|
|
134
|
-
name="dynamically_loaded_module",
|
|
135
|
-
location=file_path,
|
|
136
|
-
)
|
|
137
|
-
if mspec is None:
|
|
138
|
-
# need to investigate what situation
|
|
139
|
-
return
|
|
140
|
-
module = importlib.util.module_from_spec(mspec)
|
|
141
|
-
|
|
142
|
-
try:
|
|
143
|
-
if auto_clear:
|
|
144
|
-
# clear previous module's drawing status
|
|
145
|
-
clear()
|
|
146
|
-
|
|
147
|
-
# call module for drawing
|
|
148
|
-
indent = " " * (self._load_stack - 1) * self._INDENT_SIZE
|
|
149
|
-
logger.info(f"{indent} - {file_path}")
|
|
150
|
-
mspec.loader.exec_module(module) # type: ignore[union-attr]
|
|
151
|
-
except Exception as e: # pylint: disable=broad-exception-caught
|
|
152
|
-
file, line, _, _ = traceback.extract_tb(e.__traceback__)[-1]
|
|
153
|
-
logger.critical(f'{type(e).__name__} at file:"{file}", line:"{line}"')
|
|
154
|
-
logger.critical(str(e))
|
|
155
|
-
logger.debug("")
|
|
156
|
-
logger.debug(traceback.format_exc())
|
|
157
|
-
sys.exit(1)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
executer = DrawlibExecuter()
|
|
161
|
-
dexec = executer.dexec
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|