#!/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.check_output('npm install'.split(), cwd='turbowarp-packager-cli') _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()