#!/usr/bin/env python3
"""Skript um eine HTML-Gallery fuer mehrere Scratch-Spiele zu erstellen"""
import argparse
import os
import pathlib
import tomllib
import shutil
import subprocess
import sys
from jinja2 import Environment, FileSystemLoader, select_autoescape
def nested_get(data_dict, key_sequence):
"""
Retrieves a value from a nested dictionary using an iterative approach.
Args:
data_dict (dict): The dictionary to traverse.
key_sequence (list): A list of keys representing the path.
Returns:
Any: The value at the specified path.
"""
current_level = data_dict
for key in key_sequence:
if key not in current_level:
return None
current_level = current_level[key]
return current_level
def nested_set(data_dict, key_sequence, value):
"""
Sets a value in a nested dictionary, creating intermediate dictionaries if needed.
Args:
data_dict (dict): The dictionary to modify.
key_sequence (list): A list of keys representing the path.
value (Any): The value to set.
"""
for key in key_sequence[:-1]:
data_dict = data_dict.setdefault(key, {})
data_dict[key_sequence[-1]] = value
def load_projects(root: str) -> dict:
"""Load all game meta data from the games directory"""
projects = {}
for _parent, dirs, files in os.walk(root):
parent = pathlib.Path(_parent)
scope = nested_get(projects, parent.parts[1:])
if scope is None and len(dirs):
scope = {}
nested_set(projects, parent.parts[1:], scope)
if not 'meta.toml' in files:
continue
with open(parent / 'meta.toml', 'rb') as meta_file:
game = tomllib.load(meta_file)
if 'Name' not in game:
if 'Datei' in game:
game['Name'] = pathlib.Path(game['Datei']).stem
else:
game['Name'] = 'Project'
if 'Team' not in game:
game['Team'] = ['anonym']
else:
game['Team'] = ', '.join(game['Team'])
if 'Pfad' not in game:
game['Pfad'] = str(parent.relative_to(root))
if 'Lizenz' not in game:
game['Lizenz'] = 'Alle Rechte vorbehalten'
elif game['Lizenz'].startswith('CC') and game['Datei'].endswith('sb3'):
game['Download'] = game['Datei']
nested_set(projects, parent.parts[1:], game)
if len(projects) > 1:
del projects['test']
return projects
def prepare_outputdir(_outdir: str, all_projects: dict):
"""Prepare the output dir recreating the project structure"""
outdir = pathlib.Path(_outdir)
for section, subsections in all_projects.items():
sectiondir = outdir / section
for subsection, projects in subsections.items():
subsectiondir = sectiondir / subsection
for title in projects:
projectdir = subsectiondir / title
os.makedirs(projectdir, exist_ok=True)
def package_projects(_indir: str, _outdir: str, all_projects: dict):
"""Package all scratch projects to html"""
# Ensure that the local turbowarp-packer-cli is available through npm
subprocess.run('npm install'.split(), cwd='turbowarp-packager-cli', check=True)
_cmd = 'npx turbowarp-packager-cli -s ../turbowarp-packager-settings.json -i "{infile}" -o "{outdir}"'
indir = pathlib.Path(_indir)
outdir = pathlib.Path(_outdir)
for section, subsections in all_projects.items():
for subsection, projects in subsections.items():
for title, project in projects.items():
print(f'package {title}', file=sys.stderr)
projectindir = indir / section / subsection / title
projectoutdir = outdir / section / subsection / title
infile = projectindir / project['Datei']
shutil.copyfile(projectindir / 'meta.toml', projectoutdir / 'meta.toml')
if 'Download' in project:
shutil.copyfile(projectindir / project['Download'],
projectoutdir / project['Download'])
# The project is not a scratch file
if not infile.suffix == '.sb3':
shutil.copyfile(infile, projectoutdir / project['Datei'])
continue
cmd = _cmd.format(infile='..' / infile, outdir='..' / projectoutdir)
subprocess.run(cmd, cwd='turbowarp-packager-cli', check=True, shell=True)
project['Datei'] = 'demo_output.html'
def main():
"""Main entry point to generate a HTML Gallery"""
parser = argparse.ArgumentParser()
parser.add_argument('--author', default='Florian Fischer', help='author mentioned in the HTML')
parser.add_argument('--title', default='Scratch Galerie', help='title of the web page')
parser.add_argument('--projects-dir', default='projects', help='location of the projects')
parser.add_argument('--no-packaging',
action='store_true',
help='do not package all encountered scratch projects')
parser.add_argument('--output-dir',
default='build',
help='output location of the generated HTML code')
args = parser.parse_args()
env = Environment(loader=FileSystemLoader("templates"), autoescape=select_autoescape())
projects = load_projects(args.projects_dir)
# print(projects, file=sys.stderr)
prepare_outputdir(args.output_dir, projects)
if not args.no_packaging:
package_projects(args.projects_dir, args.output_dir, projects)
template = env.get_template("index.html")
with open(pathlib.Path(args.output_dir) / 'index.html', 'w', encoding='utf-8') as outfile:
print(template.render(projects=projects, **args.__dict__), file=outfile)
if __name__ == '__main__':
main()