st-creator 0.2.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.
- st_creator/__init__.py +16 -0
- st_creator/commands/__init__.py +0 -0
- st_creator/commands/theme.py +137 -0
- st_creator/commands/version.py +9 -0
- st_creator/core/__init__.py +0 -0
- st_creator/core/model.py +355 -0
- st_creator/core/rule_parser.py +649 -0
- st_creator/core/theme_attributes.py +34 -0
- st_creator/utils/__init__.py +0 -0
- st_creator/utils/colors.py +40 -0
- st_creator/utils/config.py +45 -0
- st_creator/utils/pretty_print.py +87 -0
- st_creator/utils/str_utils.py +156 -0
- st_creator-0.2.0.dist-info/METADATA +180 -0
- st_creator-0.2.0.dist-info/RECORD +17 -0
- st_creator-0.2.0.dist-info/WHEEL +4 -0
- st_creator-0.2.0.dist-info/entry_points.txt +3 -0
st_creator/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from st.commands.theme import theme
|
|
4
|
+
from st.commands.version import version
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group()
|
|
8
|
+
def main():
|
|
9
|
+
"""ST: A command line tool for creating Structurizr themes easily."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
main.add_command(version)
|
|
13
|
+
main.add_command(theme)
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathlib
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from st.core.model import Theme
|
|
7
|
+
from st.core.model import ThemeElement
|
|
8
|
+
from st.core.model import go_through_fs_tree
|
|
9
|
+
from st.core.rule_parser import RuleSet
|
|
10
|
+
from st.core.rule_parser import filter_elements
|
|
11
|
+
from st.utils.config import CONFIG_FILE
|
|
12
|
+
from st.utils.config import init_config
|
|
13
|
+
from st.utils.config import load_config
|
|
14
|
+
from st.utils.pretty_print import print_done
|
|
15
|
+
from st.utils.pretty_print import print_verbose
|
|
16
|
+
from st.utils.pretty_print import print_warning
|
|
17
|
+
from st.utils.str_utils import to_clean_string
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.group()
|
|
21
|
+
def theme():
|
|
22
|
+
"""Create a DSL theme."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@theme.command()
|
|
26
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
27
|
+
@click.option("--output", type=click.Path(), required=False, default=None)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--rules",
|
|
30
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
31
|
+
required=False,
|
|
32
|
+
default=None,
|
|
33
|
+
help="Path to a rules file (e.g. rules.st) filtering which icons to process.",
|
|
34
|
+
)
|
|
35
|
+
def clean(path, output, rules):
|
|
36
|
+
"""Prepare a directory of icons for theme creation."""
|
|
37
|
+
full_path = pathlib.Path(path)
|
|
38
|
+
output_path = full_path.parent / (output if output else "cleaned")
|
|
39
|
+
if not output_path.exists():
|
|
40
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
first_level = [item for item in os.listdir(full_path) if not item.startswith(".")]
|
|
42
|
+
first_level_dict = {item: to_clean_string(item) for item in first_level}
|
|
43
|
+
for item in first_level_dict.values():
|
|
44
|
+
if item.startswith("."):
|
|
45
|
+
print_verbose(f"Skipping hidden directory: {item}")
|
|
46
|
+
else:
|
|
47
|
+
if item is not None:
|
|
48
|
+
os.makedirs(os.path.join(output_path, item), exist_ok=True)
|
|
49
|
+
elements = go_through_fs_tree(
|
|
50
|
+
full_path=full_path, include_top_parent_directory=False
|
|
51
|
+
)
|
|
52
|
+
if rules is not None:
|
|
53
|
+
elements = filter_elements(elements, RuleSet.from_file(pathlib.Path(rules)))
|
|
54
|
+
for element in elements:
|
|
55
|
+
element.copy(
|
|
56
|
+
output_path=output_path
|
|
57
|
+
/ element.top_parent_directory
|
|
58
|
+
/ element.cleaned_file_name
|
|
59
|
+
if element.top_parent_directory
|
|
60
|
+
else output_path / element.cleaned_file_name
|
|
61
|
+
)
|
|
62
|
+
print_done("Cleaning done!")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@theme.command()
|
|
66
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
67
|
+
@click.option("--name", type=str, required=True)
|
|
68
|
+
@click.option("--description", type=str, required=False, default=None)
|
|
69
|
+
@click.option(
|
|
70
|
+
"--rules",
|
|
71
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
72
|
+
required=False,
|
|
73
|
+
default=None,
|
|
74
|
+
help="Path to a rules file (e.g. rules.st) filtering which icons to process.",
|
|
75
|
+
)
|
|
76
|
+
def create(path, name: str, description: str | None, rules: str) -> None:
|
|
77
|
+
if not CONFIG_FILE.exists():
|
|
78
|
+
print_warning("Configuration file not found. Creating one with default values.")
|
|
79
|
+
init_config(config_path=CONFIG_FILE)
|
|
80
|
+
config = load_config(config_path=CONFIG_FILE)
|
|
81
|
+
full_path = pathlib.Path(path).resolve()
|
|
82
|
+
output_path = full_path.parent / "-".join(name.lower().split(" "))
|
|
83
|
+
|
|
84
|
+
name = name.replace("'", "").replace('"', "")
|
|
85
|
+
description = (
|
|
86
|
+
description.replace("'", "").replace('"', "")
|
|
87
|
+
if description is not None
|
|
88
|
+
else None
|
|
89
|
+
)
|
|
90
|
+
os.makedirs(os.path.join(output_path), exist_ok=True)
|
|
91
|
+
theme = Theme(
|
|
92
|
+
name="Name here" if name is None else name,
|
|
93
|
+
description="Description here" if description is None else description,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
elements = go_through_fs_tree(
|
|
97
|
+
full_path=full_path,
|
|
98
|
+
include_top_parent_directory=False,
|
|
99
|
+
)
|
|
100
|
+
if rules is not None:
|
|
101
|
+
elements = filter_elements(elements, RuleSet.from_file(pathlib.Path(rules)))
|
|
102
|
+
|
|
103
|
+
for element in elements:
|
|
104
|
+
element.copy(
|
|
105
|
+
output_path=output_path / (theme.file_prefix + "_" + element.theme_name),
|
|
106
|
+
)
|
|
107
|
+
element.load_properties_from_json()
|
|
108
|
+
|
|
109
|
+
theme_element = ThemeElement(
|
|
110
|
+
tag=element.get_theme_tag(prefix=theme.tag_prefix),
|
|
111
|
+
# stroke=color
|
|
112
|
+
# if color is not None
|
|
113
|
+
# else config["defaults.theme"]["stroke"],
|
|
114
|
+
# color=color if color is not None else config["defaults.theme"]["color"],
|
|
115
|
+
stroke="#232f3d",
|
|
116
|
+
color="#232f3d",
|
|
117
|
+
icon=theme.file_prefix + "_" + element.theme_name,
|
|
118
|
+
)
|
|
119
|
+
theme.add_element(theme_element)
|
|
120
|
+
|
|
121
|
+
theme.export(pathlib.Path(output_path) / "theme.json")
|
|
122
|
+
|
|
123
|
+
print_done("Theme created!")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# TODO: keep this for future releases maybe >^.^<
|
|
127
|
+
# @click.command()
|
|
128
|
+
# @click.option("--output", type=click.Path(), required=True)
|
|
129
|
+
# def scrape(output) -> None:
|
|
130
|
+
# print(f"Scraping icons from {TECH_ICON_URL}\n")
|
|
131
|
+
# time.sleep(1)
|
|
132
|
+
# if download_blob_image(
|
|
133
|
+
# base_url=TECH_ICON_URL, output_dir=pathlib.Path(output).resolve()
|
|
134
|
+
# ):
|
|
135
|
+
# print_done("\nAll images downloaded successfully!")
|
|
136
|
+
# else:
|
|
137
|
+
# print_warning("\nDownload failed")
|
|
File without changes
|
st_creator/core/model.py
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
import shutil
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from st.core.theme_attributes import Border
|
|
8
|
+
from st.core.theme_attributes import Shape
|
|
9
|
+
from st.utils.pretty_print import print_ok
|
|
10
|
+
from st.utils.pretty_print import print_verbose
|
|
11
|
+
from st.utils.pretty_print import print_warning
|
|
12
|
+
from st.utils.str_utils import from_snake_to_camel_case
|
|
13
|
+
from st.utils.str_utils import to_clean_string
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IconElement:
|
|
17
|
+
"""Class representing a icon in the file system.
|
|
18
|
+
|
|
19
|
+
The entire CLI is based on the file system structure and the icons found.
|
|
20
|
+
This class is the starting point for DSL theme elements, as it represents
|
|
21
|
+
the icons found.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, path: pathlib.Path, include_top_parent_directory: bool = True):
|
|
25
|
+
"""Initialize a IconElement instance."""
|
|
26
|
+
self.path = path
|
|
27
|
+
self._include_top_parent_directory = include_top_parent_directory
|
|
28
|
+
self.top_parent_directory = (
|
|
29
|
+
pathlib.Path(to_clean_string(path.parts[1]))
|
|
30
|
+
if len(path.parts) > 2
|
|
31
|
+
else None
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
self.metadata: dict[str, Any] = {}
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def name(self) -> str:
|
|
38
|
+
"""Get the name of the icon element without the file extension.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
str: The name of the icon element without the file extension.
|
|
42
|
+
"""
|
|
43
|
+
return self.path.stem
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def full_name(self) -> str:
|
|
47
|
+
"""Get the full name of the icon element with the file extension.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
str: The full name of the icon element with the file extension.
|
|
51
|
+
"""
|
|
52
|
+
return self.path.name
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def extension(self) -> str:
|
|
56
|
+
"""Get the file extension of the icon element.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
str: The file extension of the icon element.
|
|
60
|
+
"""
|
|
61
|
+
return self.path.suffix
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def cleaned_file_name(self) -> str:
|
|
65
|
+
"""Get the output file path for the icon element.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
str: The output file path for the icon element.
|
|
69
|
+
"""
|
|
70
|
+
if (
|
|
71
|
+
self.top_parent_directory is not None
|
|
72
|
+
) and self._include_top_parent_directory:
|
|
73
|
+
return (
|
|
74
|
+
self.top_parent_directory.name
|
|
75
|
+
+ "_"
|
|
76
|
+
+ to_clean_string(self.name)
|
|
77
|
+
+ self.extension
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
return to_clean_string(self.name) + self.extension
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def theme_name(self) -> str:
|
|
84
|
+
theme_name = self.path.parent.parts[-1].split("_")[0].lower() + "_" + self.name
|
|
85
|
+
return theme_name + self.extension
|
|
86
|
+
|
|
87
|
+
def get_theme_tag(self, prefix: str | None = None) -> str:
|
|
88
|
+
theme_name = self.theme_name.split(".")[0]
|
|
89
|
+
tag_name = (
|
|
90
|
+
theme_name.split("_")[0].capitalize()
|
|
91
|
+
+ " - "
|
|
92
|
+
+ " ".join([word.capitalize() for word in theme_name.split("_")[1:]])
|
|
93
|
+
)
|
|
94
|
+
if prefix is not None:
|
|
95
|
+
tag_name = prefix + " - " + tag_name
|
|
96
|
+
|
|
97
|
+
return tag_name
|
|
98
|
+
|
|
99
|
+
# TODO: find a way to manage this logic in a more elegant way
|
|
100
|
+
def load_properties_from_json(self):
|
|
101
|
+
"""Load propertiers from a JSON file located alongside the element SVG file.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
path (str): Path to the JSON file containing metadata.
|
|
105
|
+
"""
|
|
106
|
+
try:
|
|
107
|
+
json_file = self.path.resolve().stem + ".json"
|
|
108
|
+
json_file_path = self.path.parent / json_file
|
|
109
|
+
|
|
110
|
+
with open(json_file_path) as f:
|
|
111
|
+
self.metadata = json.load(f)
|
|
112
|
+
print_verbose(f"Loaded metadata for {self.name}")
|
|
113
|
+
except FileNotFoundError:
|
|
114
|
+
self.metadata = {}
|
|
115
|
+
|
|
116
|
+
def copy(
|
|
117
|
+
self,
|
|
118
|
+
output_path: pathlib.Path,
|
|
119
|
+
):
|
|
120
|
+
"""Copy the icon element to a new location.
|
|
121
|
+
|
|
122
|
+
This is particularly to correctly manage icons for the DSL theme.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
output_path (pathlib.Path): The path to copy the icon element to.
|
|
126
|
+
"""
|
|
127
|
+
shutil.copyfile(
|
|
128
|
+
self.path,
|
|
129
|
+
output_path,
|
|
130
|
+
)
|
|
131
|
+
print_ok("Processed " + self.full_name)
|
|
132
|
+
|
|
133
|
+
def __str__(self) -> str:
|
|
134
|
+
"""Return a string representation of the IconElement instance.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
str: A string representation of the IconElement instance.
|
|
138
|
+
"""
|
|
139
|
+
return f"Element(name={self.name}, path={self.path})"
|
|
140
|
+
|
|
141
|
+
def __repr__(self) -> str:
|
|
142
|
+
"""Return a string representation of the IconElement instance.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
str: A string representation of the IconElement instance.
|
|
146
|
+
"""
|
|
147
|
+
repr_str = "IconElement("
|
|
148
|
+
for key, value in self.__class__.__dict__.items():
|
|
149
|
+
if not key.startswith("__") and not callable(value):
|
|
150
|
+
repr_str += f"{key}={getattr(self, key)}, "
|
|
151
|
+
repr_str = repr_str.rstrip(", ") + ")"
|
|
152
|
+
return repr_str
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class ThemeElement:
|
|
156
|
+
"""Class representing a theme element for the DSL theme."""
|
|
157
|
+
|
|
158
|
+
def __init__(
|
|
159
|
+
self,
|
|
160
|
+
tag: str | None = None,
|
|
161
|
+
stroke: str | None = None,
|
|
162
|
+
color: str | None = None,
|
|
163
|
+
icon: str | None = None,
|
|
164
|
+
border: Border | None = Border.SOLID,
|
|
165
|
+
shape: Shape | None = Shape.ROUDED_BOX,
|
|
166
|
+
width: int | None = None,
|
|
167
|
+
height: int | None = None,
|
|
168
|
+
background: str | None = None,
|
|
169
|
+
font_size: int | None = None,
|
|
170
|
+
opacity: int | None = None,
|
|
171
|
+
description: str | None = None,
|
|
172
|
+
):
|
|
173
|
+
"""Initialize a ThemeElement instance.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
tag (str): The tag for the theme element.
|
|
177
|
+
stroke (str): The stroke color for the theme element.
|
|
178
|
+
color (str): The fill color for the theme element.
|
|
179
|
+
icon (str): The icon for the theme element.
|
|
180
|
+
shape (Shape): The shape of the theme element.
|
|
181
|
+
width (int): The width of the theme element.
|
|
182
|
+
height (int): The height of the theme element.
|
|
183
|
+
background (str): The background color for the theme element.
|
|
184
|
+
font_size (int): The font size for the theme element.
|
|
185
|
+
border (Border): The border style for the theme element.
|
|
186
|
+
opacity (int): The opacity for the theme element.
|
|
187
|
+
description (str): A brief description of the theme element.
|
|
188
|
+
"""
|
|
189
|
+
self.tag = tag
|
|
190
|
+
self.stroke = stroke
|
|
191
|
+
self.color = color
|
|
192
|
+
self.icon = icon
|
|
193
|
+
self.shape = shape.value if shape is not None else None
|
|
194
|
+
self.width = width
|
|
195
|
+
self.height = height
|
|
196
|
+
self.background = background
|
|
197
|
+
self.font_size = font_size
|
|
198
|
+
self.border = border.value if border is not None else None
|
|
199
|
+
self.opacity = opacity
|
|
200
|
+
self.description = description
|
|
201
|
+
|
|
202
|
+
def as_dict(self) -> dict[str, Any]:
|
|
203
|
+
"""Convert the ThemeElement instance to a dictionary. Skip None attribues.
|
|
204
|
+
|
|
205
|
+
Dictionary names are the final names used in the DSL theme.
|
|
206
|
+
To do so, snake_case attribute names are converted to camelCase.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
dict: A dictionary representation of the ThemeElement instance.
|
|
210
|
+
"""
|
|
211
|
+
dict_representation = {}
|
|
212
|
+
# Iterate over class attributes
|
|
213
|
+
for attribute, value in self.__dict__.items():
|
|
214
|
+
if value is not None:
|
|
215
|
+
dict_representation[from_snake_to_camel_case(attribute)] = value
|
|
216
|
+
return dict_representation
|
|
217
|
+
|
|
218
|
+
@classmethod
|
|
219
|
+
def from_json_dict(cls, dict: dict[str, Any]) -> "ThemeElement":
|
|
220
|
+
"""Create a ThemeElement instance from a dictionary.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
dict (dict): A dictionary of the attributes of the ThemeElement.
|
|
224
|
+
As it is in the theme. Thus, the keys are in camelCase.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
ThemeElement: A new instance of ThemeElement.
|
|
228
|
+
"""
|
|
229
|
+
theme_element = cls()
|
|
230
|
+
for key, value in dict.items():
|
|
231
|
+
if hasattr(theme_element, key) and key != "tag":
|
|
232
|
+
setattr(theme_element, key, value)
|
|
233
|
+
else:
|
|
234
|
+
print_warning(f"Unknown attribute {key} in ThemeElement JSON.")
|
|
235
|
+
return theme_element
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def get_tag_theme_prefix(theme_name: str) -> str:
|
|
239
|
+
"""Get the prefix for a theme based on its name.
|
|
240
|
+
|
|
241
|
+
The prefix is generated by taking the first letter of each word in the theme name,
|
|
242
|
+
converted to a clean string, that originally had more than 3 characters.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
theme_name (str): The name of the theme.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
str: The prefix for the theme.
|
|
249
|
+
"""
|
|
250
|
+
eligible_words = [
|
|
251
|
+
to_clean_string(word) for word in theme_name.split() if len(word) > 2
|
|
252
|
+
]
|
|
253
|
+
prefix = (
|
|
254
|
+
"".join([word[0].lower() for word in eligible_words])
|
|
255
|
+
if len(eligible_words) > 2
|
|
256
|
+
else to_clean_string(theme_name)
|
|
257
|
+
)
|
|
258
|
+
return prefix
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def get_file_theme_prefix(theme_name: str) -> str:
|
|
262
|
+
"""Get the prefix for a theme based on its name.
|
|
263
|
+
|
|
264
|
+
The prefix is generated by taking the first letter of each word in the theme name,
|
|
265
|
+
converted to a clean string, that originally had more than 3 characters.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
theme_name (str): The name of the theme.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
str: The prefix for the theme.
|
|
272
|
+
"""
|
|
273
|
+
eligible_words = [
|
|
274
|
+
to_clean_string(word) for word in theme_name.split() if len(word) > 2
|
|
275
|
+
]
|
|
276
|
+
prefix = (
|
|
277
|
+
"".join([word[0].lower() for word in eligible_words])
|
|
278
|
+
if len(eligible_words) > 2
|
|
279
|
+
else to_clean_string(theme_name)
|
|
280
|
+
)
|
|
281
|
+
return prefix
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class Theme:
|
|
285
|
+
"""Class representing a theme, which is a collection of theme elements."""
|
|
286
|
+
|
|
287
|
+
def __init__(self, name: str, description: str):
|
|
288
|
+
"""Initialize a Theme instance.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
name (str): The name of the theme.
|
|
292
|
+
description (str): A brief description of the theme.
|
|
293
|
+
"""
|
|
294
|
+
self.name = name
|
|
295
|
+
self.description = description
|
|
296
|
+
self.tag_prefix = get_tag_theme_prefix(name)
|
|
297
|
+
self.file_prefix = get_file_theme_prefix(name)
|
|
298
|
+
self.elements: list[ThemeElement] = []
|
|
299
|
+
|
|
300
|
+
def add_element(self, element: ThemeElement) -> None:
|
|
301
|
+
"""Add a theme element to the theme.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
element (ThemeElement): The theme element to add.
|
|
305
|
+
"""
|
|
306
|
+
self.elements.append(element)
|
|
307
|
+
|
|
308
|
+
def extend(self, elements: list[ThemeElement]) -> None:
|
|
309
|
+
"""Extend the theme with a list of theme elements.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
elements (list[ThemeElement]): A list of theme elements to add.
|
|
313
|
+
"""
|
|
314
|
+
self.elements.extend(elements)
|
|
315
|
+
|
|
316
|
+
def export(self, path: pathlib.Path) -> None:
|
|
317
|
+
"""Export the theme to a JSON file.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
path (pathlib.Path): The path to the JSON file.
|
|
321
|
+
"""
|
|
322
|
+
json_dict = {
|
|
323
|
+
"name": self.name,
|
|
324
|
+
"description": self.description,
|
|
325
|
+
"elements": [element.as_dict() for element in self.elements],
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
with open(path, "w") as f:
|
|
329
|
+
json.dump(json_dict, f, indent=4)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def go_through_fs_tree(
|
|
333
|
+
full_path: pathlib.Path,
|
|
334
|
+
include_top_parent_directory: bool,
|
|
335
|
+
) -> list[IconElement]:
|
|
336
|
+
element_list = []
|
|
337
|
+
for item in os.listdir(full_path):
|
|
338
|
+
item_path = pathlib.Path(full_path) / item
|
|
339
|
+
if os.path.isfile(item_path):
|
|
340
|
+
if item.startswith("."):
|
|
341
|
+
print_verbose(
|
|
342
|
+
f"Skipping hidden file: {'/'.join(str(item_path).split('/')[-3:])}"
|
|
343
|
+
)
|
|
344
|
+
continue
|
|
345
|
+
element_list.append(IconElement(item_path, include_top_parent_directory))
|
|
346
|
+
|
|
347
|
+
elif os.path.isdir(item_path):
|
|
348
|
+
element_list.extend(
|
|
349
|
+
go_through_fs_tree(
|
|
350
|
+
full_path=item_path,
|
|
351
|
+
include_top_parent_directory=include_top_parent_directory,
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
return element_list
|