338 lines
11 KiB
Python
Executable file
338 lines
11 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
"""
|
|
Create requirement screenshots for learn guide projects
|
|
"""
|
|
|
|
# SPDX-FileCopyrightText: 2021 foamyguy
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
from multiprocessing import Pool
|
|
import json
|
|
import os
|
|
import traceback
|
|
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
from get_imports import (
|
|
get_libs_for_project,
|
|
get_files_for_project,
|
|
get_learn_guide_cp_projects,
|
|
)
|
|
|
|
os.makedirs("generated_images", exist_ok=True)
|
|
|
|
OUT_WIDTH = 800
|
|
PADDING = 20
|
|
|
|
INDENT_SIZE = 28
|
|
LINE_SPACING = 28
|
|
HIGHLIGHT_ROW_COLOR = "#404040"
|
|
ROW_COLOR = "#383838"
|
|
|
|
TEXT_COLOR = "#B0B0B0"
|
|
HIDDEN_TEXT_COLOR = "#808080"
|
|
|
|
SHOWN_FILETYPES = ["py", "mpy", "bmp", "pcf", "bdf", "wav", "mp3", "json", "txt"]
|
|
|
|
f = open("latest_bundle_data.json", "r")
|
|
bundle_data = json.load(f)
|
|
f.close()
|
|
|
|
font = ImageFont.truetype("Roboto-Regular.ttf", 24)
|
|
right_triangle = Image.open("img/right_triangle.png")
|
|
down_triangle = Image.open("img/down_triangle.png")
|
|
|
|
folder_icon = Image.open("img/folder.png")
|
|
folder_hidden_icon = Image.open("img/folder_hidden.png")
|
|
file_icon = Image.open("img/file.png")
|
|
file_hidden_icon = Image.open("img/file_hidden.png")
|
|
file_empty_icon = Image.open("img/file_empty.png")
|
|
file_empty_hidden_icon = Image.open("img/file_empty_hidden.png")
|
|
|
|
file_image_icon = Image.open("img/file_image.png")
|
|
file_music_icon = Image.open("img/file_music.png")
|
|
file_font_icon = Image.open("img/file_font.png")
|
|
|
|
FILE_TYPE_ICON_MAP = {
|
|
"py": file_icon,
|
|
"mpy": file_icon,
|
|
"txt": file_empty_icon,
|
|
"bmp": file_image_icon,
|
|
"wav": file_music_icon,
|
|
"mp3": file_music_icon,
|
|
"pcf": file_font_icon,
|
|
"bdf": file_font_icon,
|
|
"json": file_icon,
|
|
}
|
|
|
|
# If this is not done, the images fail to load in the subprocesses.
|
|
for file_icon in FILE_TYPE_ICON_MAP.values():
|
|
file_icon.load()
|
|
|
|
|
|
def generate_requirement_image(
|
|
learn_guide_project,
|
|
): # pylint: disable=too-many-statements
|
|
"""Generate a single requirement image"""
|
|
|
|
def make_line(
|
|
requirement_name, position=(0, 0), icon=None, hidden=False, triangle_icon=None
|
|
): # pylint: disable=too-many-branches
|
|
if triangle_icon:
|
|
img.paste(
|
|
triangle_icon,
|
|
(position[0] - 24, position[1] + (LINE_SPACING - 24) // 2),
|
|
mask=triangle_icon,
|
|
)
|
|
if icon is None:
|
|
if requirement_name.endswith(".mpy") or requirement_name.endswith(".py"):
|
|
if hidden:
|
|
img.paste(
|
|
file_hidden_icon,
|
|
(position[0], position[1] + (LINE_SPACING - 24) // 2),
|
|
mask=file_hidden_icon,
|
|
)
|
|
else:
|
|
img.paste(
|
|
file_icon,
|
|
(position[0], position[1] + (LINE_SPACING - 24) // 2),
|
|
mask=file_icon,
|
|
)
|
|
|
|
elif "." in requirement_name[-5:]:
|
|
if hidden:
|
|
img.paste(
|
|
file_empty_hidden_icon,
|
|
(position[0], position[1] + (LINE_SPACING - 24) // 2),
|
|
mask=file_empty_icon,
|
|
)
|
|
else:
|
|
img.paste(
|
|
file_empty_icon,
|
|
(position[0], position[1] + (LINE_SPACING - 24) // 2),
|
|
mask=file_empty_icon,
|
|
)
|
|
else:
|
|
if hidden:
|
|
img.paste(
|
|
folder_hidden_icon,
|
|
(position[0], position[1] + (LINE_SPACING - 24) // 2),
|
|
mask=folder_hidden_icon,
|
|
)
|
|
else:
|
|
img.paste(
|
|
folder_icon,
|
|
(position[0], position[1] + (LINE_SPACING - 24) // 2),
|
|
mask=folder_icon,
|
|
)
|
|
else:
|
|
img.paste(
|
|
icon, (position[0], position[1] + (LINE_SPACING - 24) // 2), mask=icon
|
|
)
|
|
|
|
if not hidden:
|
|
draw.text(
|
|
(position[0] + 30, position[1] + LINE_SPACING // 2),
|
|
requirement_name,
|
|
fill=TEXT_COLOR,
|
|
anchor="lm",
|
|
font=font,
|
|
)
|
|
else:
|
|
draw.text(
|
|
(position[0] + 30, position[1] + LINE_SPACING // 2),
|
|
requirement_name,
|
|
fill=HIDDEN_TEXT_COLOR,
|
|
anchor="lm",
|
|
font=font,
|
|
)
|
|
|
|
def make_header(position, learn_guide_project):
|
|
# Static files
|
|
make_line("CIRCUITPY", position)
|
|
make_line(
|
|
".fseventsd",
|
|
(position[0] + INDENT_SIZE, position[1] + (LINE_SPACING * 1)),
|
|
hidden=True,
|
|
triangle_icon=right_triangle,
|
|
)
|
|
make_line(
|
|
".metadata_never_index",
|
|
(position[0] + INDENT_SIZE, position[1] + (LINE_SPACING * 2)),
|
|
icon=file_empty_hidden_icon,
|
|
hidden=True,
|
|
)
|
|
make_line(
|
|
".Trashes",
|
|
(position[0] + INDENT_SIZE, position[1] + (LINE_SPACING * 3)),
|
|
icon=file_empty_hidden_icon,
|
|
hidden=True,
|
|
)
|
|
make_line(
|
|
"boot_out.txt",
|
|
(position[0] + INDENT_SIZE, position[1] + (LINE_SPACING * 4)),
|
|
)
|
|
make_line(
|
|
"code.py",
|
|
(position[0] + INDENT_SIZE, position[1] + (LINE_SPACING * 5)),
|
|
icon=file_icon,
|
|
)
|
|
|
|
# dynamic files from project dir in learn guide repo
|
|
project_files = get_files_for_project(learn_guide_project)
|
|
rows_added = 0
|
|
project_files_to_draw = []
|
|
project_folders_to_draw = []
|
|
for cur_file in project_files:
|
|
if "." in cur_file[-5:]:
|
|
cur_extension = cur_file.split(".")[-1]
|
|
if cur_extension in SHOWN_FILETYPES:
|
|
if cur_file != "main.py":
|
|
project_files_to_draw.append(cur_file)
|
|
else:
|
|
project_folders_to_draw.append(cur_file)
|
|
|
|
try:
|
|
project_files_to_draw.remove("code.py")
|
|
except ValueError:
|
|
pass
|
|
|
|
for i, file in enumerate(sorted(project_files_to_draw)):
|
|
cur_file_extension = file.split(".")[-1]
|
|
|
|
cur_file_icon = FILE_TYPE_ICON_MAP.get(cur_file_extension, file_empty_icon)
|
|
make_line(
|
|
file,
|
|
(position[0] + INDENT_SIZE, position[1] + (LINE_SPACING * (6 + i))),
|
|
icon=cur_file_icon,
|
|
)
|
|
rows_added += 1
|
|
|
|
for i, file in enumerate(sorted(project_folders_to_draw)):
|
|
make_line(
|
|
file,
|
|
(
|
|
position[0] + INDENT_SIZE,
|
|
position[1] + (LINE_SPACING * (6 + i + len(project_files_to_draw))),
|
|
),
|
|
triangle_icon=right_triangle,
|
|
)
|
|
rows_added += 1
|
|
|
|
make_line(
|
|
"lib",
|
|
(
|
|
position[0] + INDENT_SIZE,
|
|
position[1] + (LINE_SPACING * (5 + rows_added + 1)),
|
|
),
|
|
triangle_icon=down_triangle,
|
|
)
|
|
|
|
def make_background_highlights(rows, offset=(0, 0)):
|
|
for i in range(rows):
|
|
if i % 2 == 0:
|
|
draw.rectangle(
|
|
[
|
|
(0 + offset[0], i * LINE_SPACING + offset[1]),
|
|
(OUT_WIDTH - offset[0], (i + 1) * LINE_SPACING + offset[1]),
|
|
],
|
|
fill=HIGHLIGHT_ROW_COLOR,
|
|
)
|
|
else:
|
|
draw.rectangle(
|
|
[
|
|
(0 + offset[0], i * LINE_SPACING + offset[1]),
|
|
(OUT_WIDTH - offset[0], (i + 1) * LINE_SPACING + offset[1]),
|
|
],
|
|
fill=ROW_COLOR,
|
|
)
|
|
|
|
def get_dependencies(libraries):
|
|
package_list = set()
|
|
file_list = set()
|
|
|
|
libraries_to_check = list(libraries)
|
|
|
|
while len(libraries_to_check) > 0:
|
|
lib_name = libraries_to_check[0]
|
|
del libraries_to_check[0]
|
|
|
|
lib_obj = bundle_data[lib_name]
|
|
for dep_name in lib_obj["dependencies"]:
|
|
libraries_to_check.append(dep_name)
|
|
dep_obj = bundle_data[dep_name]
|
|
if dep_obj["package"]:
|
|
package_list.add(dep_name)
|
|
else:
|
|
file_list.add(dep_name + ".mpy")
|
|
|
|
if lib_obj["package"]:
|
|
package_list.add(lib_name)
|
|
else:
|
|
file_list.add(lib_name + ".mpy")
|
|
|
|
return package_list, file_list
|
|
|
|
def sort_libraries(libraries):
|
|
package_list, file_list = get_dependencies(libraries)
|
|
return sorted(package_list) + sorted(file_list)
|
|
|
|
def make_libraries(libraries, position):
|
|
|
|
for i, lib_name in enumerate(libraries):
|
|
triangle_icon = None
|
|
if not lib_name.endswith(".mpy"):
|
|
triangle_icon = right_triangle
|
|
make_line(
|
|
lib_name,
|
|
(position[0] + INDENT_SIZE * 2, position[1] + (LINE_SPACING * i)),
|
|
triangle_icon=triangle_icon,
|
|
)
|
|
|
|
try:
|
|
libs = get_libs_for_project(learn_guide_project)
|
|
final_list_to_render = sort_libraries(libs)
|
|
|
|
project_file_list = get_files_for_project(learn_guide_project)
|
|
|
|
project_files_count = len(project_file_list)
|
|
|
|
if "code.py" in project_file_list:
|
|
project_files_count -= 1
|
|
|
|
if "main.py" in project_file_list:
|
|
project_files_count -= 1
|
|
|
|
image_height = (
|
|
PADDING * 2
|
|
+ 7 * LINE_SPACING
|
|
+ len(final_list_to_render) * LINE_SPACING
|
|
+ (project_files_count) * LINE_SPACING
|
|
)
|
|
img = Image.new("RGB", (OUT_WIDTH, image_height), "#303030")
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
make_background_highlights(
|
|
7 + len(final_list_to_render) + project_files_count,
|
|
offset=(PADDING, PADDING),
|
|
)
|
|
|
|
make_header((PADDING, PADDING), learn_guide_project)
|
|
make_libraries(
|
|
final_list_to_render,
|
|
(PADDING, PADDING + (LINE_SPACING * (7 + project_files_count))),
|
|
)
|
|
|
|
img.save("generated_images/{}.png".format(learn_guide_project))
|
|
except SyntaxError as exc:
|
|
print(exc)
|
|
traceback.print_exc()
|
|
print("SyntaxError finding imports for {}".format(learn_guide_project))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
with Pool() as p:
|
|
for _ in p.imap(generate_requirement_image, get_learn_guide_cp_projects()):
|
|
pass
|