diff --git a/code/backlog.txt b/code/backlog.txt index 34228bc3..dd290f16 100644 --- a/code/backlog.txt +++ b/code/backlog.txt @@ -210,6 +210,7 @@ https://github.com/AtomicGameEngine/AtomicGameEngine https://github.com/atphalix/nexuiz https://github.com/azhirnov/FrameGraph https://github.com/benl23x5/gloss +https://github.com/bepu/bepuphysics2 https://github.com/bernardosulzbach/dungeon https://github.com/bioglaze/aether3d https://github.com/bomblik/BlockOut_II_PSVITA @@ -243,6 +244,7 @@ https://github.com/DethRaid/SanityEngine https://github.com/Donerkebap13/DonerComponents https://github.com/Drasky-Vanderhoff/CommonDrops https://github.com/Dzierzan/OpenSA +https://github.com/EasyRPG/Editor-Qt https://github.com/EaW-Team/equestria_dev https://github.com/EliFUT/android https://github.com/elishacloud/Silent-Hill-2-Enhancements @@ -300,8 +302,10 @@ https://github.com/libretro/libretro-chailove https://github.com/libretro/libretro-prboom https://github.com/ligurio/awesome-ttygames https://github.com/luciopanepinto/pacman +https://github.com/LuminoEngine/Lumino https://github.com/MarcoLizza/tofu-engine https://github.com/MarilynDafa/Bulllord-Engine +https://github.com/Martenfur/Monofoxe https://github.com/MatthewTheGlutton/HideousDestructor https://github.com/McKay42/McOsu https://github.com/megamarc/Tilengine @@ -326,6 +330,7 @@ https://github.com/OpenRA/d2 https://github.com/OpenRA/OpenRAModSDK https://github.com/opensourcedesign https://github.com/opentrack/opentrack +https://github.com/OrionFive/Hospitality https://github.com/OSSGames https://github.com/OSSGames (all there, but we should have them already) https://github.com/ozkriff/zemeroth @@ -404,6 +409,7 @@ https://github.com/wojtekpil/Godot-Octahedral-Impostors https://github.com/xrOxygen/xray-oxygen https://github.com/YuriiSalimov/15-puzzle https://github.com/Zal0/ZGB +https://github.com/zcaliptium/gdinv https://github.com/zurn/zapper (or any other tapper clone) https://gitlab.com/LibreGames https://gitlab.com/nyov/nyovs-nexuiz diff --git a/code/generate_static_website.py b/code/generate_static_website.py index 319515fe..27b8dbeb 100644 --- a/code/generate_static_website.py +++ b/code/generate_static_website.py @@ -4,15 +4,41 @@ Generates the static website Uses Jinja2 (see https://jinja.palletsprojects.com/en/2.11.x/) """ +# TODO index.html tiles, content +# TODO index.html image +# TODO index.html only count games +# TODO Font awesome 5 (icons for OS, for Github, Gitlab and maybe others) +# TODO contribute.html tiles? content +# TODO games pages links to licenses (wikipedia) +# TODO games pages edit/contribute button +# TODO indexes: make categories bold that have a certain amount of entries! +# TODO everywhere: style URLs (Github, Wikipedia, Internet archive, SourceForge, ...) +# TODO developers pages links to games and more information, styles, block around entry, edit/contribute button +# TODO inspirations pages, add link to games and more information, styles, block around entry, edit/contribute button +# TODO navbar add is active +# TODO statistics page: better and more statistics with links where possible +# TODO meaningful information (links, license, last updated with lower precision) +# TODO singular, plural everywhere (game, entries, items) + import os import shutil import math import datetime +from functools import partial from utils import osg, constants as c, utils from jinja2 import Environment, FileSystemLoader +import html5lib + +alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +extra = '0' +extended_alphabet = alphabet + extra + +games_path = 'games' +inspirations_path = 'inspirations' +developers_path = 'developers' + +html5parser = html5lib.HTMLParser(strict=True) -alphabet = 'abcdefghijklmnopqrstuvwxyz' -extended_alphabet = '0' + alphabet def write(text, file): """ @@ -20,14 +46,23 @@ def write(text, file): :param text: :param file: """ + # validate text + try: + html5parser.parse(text) + except Exception as e: + utils.write_text(os.path.join(c.web_path, 'invalid.html'), text) # for further checking with https://validator.w3.org/ + raise RuntimeError(e) + # output file file = os.path.join(c.web_path, file) + # create output directory if necessary containing_dir = os.path.dirname(file) if not os.path.isdir(containing_dir): os.mkdir(containing_dir) + # write text utils.write_text(file, text) -def sort_into_categories(list, categories, fit, unknown_category_name): +def sort_into_categories(list, categories, fit, unknown_category_name=None): """ :param list: @@ -46,16 +81,8 @@ def sort_into_categories(list, categories, fit, unknown_category_name): categorized_sublists[unknown_category_name] = sublist return categorized_sublists -def sort_by_alphabet(list, key): - """ - :param list: - :param key: - :return: - """ - return sort_into_categories(list, alphabet, lambda item, category: item[key].lower().startswith(category), '0') - -def divide_in_columns(categorized_lists, key): +def divide_in_columns(categorized_lists, transform): """ :param categorized_lists: @@ -66,7 +93,8 @@ def divide_in_columns(categorized_lists, key): entries = {} for category in categorized_lists.keys(): e = categorized_lists[category] - e = [e[key] for e in e] + # transform entry + e = [transform(e) for e in e] # divide in three equal lists n = len(e) n1 = math.ceil(n/3) @@ -76,6 +104,118 @@ def divide_in_columns(categorized_lists, key): return {'number_entries': number_entries, 'entries': entries} +def url_to(current, target): + """ + + :param current: + :param target: + :return: + """ + # split by slash + if current: + current = current.split('/') + target = target.split('/') + # reduce by common elements + while len(current) > 0 and len(target) > 1 and current[0] == target[0]: + current = current[1:] + target = target[1:] + # add .. as often as length of current still left + target = ['..'] * len(current) + target + # join by slash again + url = '/'.join(target) + return url + + +def preprocess(list, key, path): + """ + + :param list: + :param key: + :return: + """ + _ = set() + for item in list: + # add unique anchor ref + anchor = osg.canonical_name(item[key]) + while anchor in _: + anchor += 'x' + _.add(anchor) + item['anchor-id'] = anchor + + # for alphabetic sorting + start = item[key][0].upper() + if not start in alphabet: + start = extra + item['letter'] = start + item['href'] = os.path.join(path, '{}.html#{}'.format(start, anchor)) + + +def game_index(entry): + e = { + 'name': entry['Title'], + 'href': entry['href'], + 'anchor-id': entry['anchor-id'] + } + tags = [] + if 'beta' in entry['State']: + tags.append('beta') + if osg.is_inactive(entry): + tags.append('inactive since {}'.format(osg.extract_inactive_year(entry))) + if tags: + e['tags'] = ', '.join(tags) + return e + + +def inspiration_index(inspiration): + e = { + 'name': inspiration['Name'], + 'href': inspiration['href'], + 'anchor-id': inspiration['anchor-id'], + } + n = len(inspiration['Inspired entries']) + if n > 1: + e['tags'] = n + return e + + +def developer_index(developer): + e = { + 'name': developer['Name'], + 'href': developer['href'], + 'anchor-id': developer['anchor-id'] + } + n = len(developer['Games']) + if n > 1: + e['tags'] = n + return e + + +def add_entries_links_to_inspirations(inspirations, entries): + entries_references = {entry['Title']:entry['href'] for entry in entries} + for inspiration in inspirations: + inspired_entries = inspiration['Inspired entries'] + inspired_entries = [(entries_references[entry], entry) for entry in inspired_entries] + inspiration['Inspired entries'] = inspired_entries + + +def add_entries_links_to_developers(developers, entries): + entries_references = {entry['Title']:entry['href'] for entry in entries} + for developer in developers: + developed_entries = developer['Games'] + developed_entries = [(entries_references[entry], entry) for entry in developed_entries] + developer['Games'] = developed_entries + + +def add_inspirations_and_developers_links_to_entries(entries, inspirations, developers): + inspirations_references = {inspiration['Name']: inspiration['href'] for inspiration in inspirations} + developer_references = {developer['Name']: developer['href'] for developer in developers} + for entry in entries: + if 'Inspirations' in entry: + entry['Inspirations'] = [(inspirations_references[inspiration.value], inspiration.value) for inspiration in entry['Inspirations']] + if 'Developer' in entry: + entry['Developer'] = [(developer_references[developer.value], developer.value) for developer in entry['Developer']] + + def generate(entries, inspirations, developers): """ @@ -84,21 +224,29 @@ def generate(entries, inspirations, developers): :param developers: """ - # add anchor ref () to every entry - for entry in entries: - entry['title-anchor'] = osg.canonical_entry_name(entry['Title']) + # preprocess + preprocess(entries, 'Title', games_path) + preprocess(inspirations, 'Name', inspirations_path) + preprocess(developers, 'Name', developers_path) + + # set internal links up + add_entries_links_to_inspirations(inspirations, entries) + add_entries_links_to_developers(developers, entries) + add_inspirations_and_developers_links_to_entries(entries, inspirations, developers) + + # sort into categories + games_by_alphabet = sort_into_categories(entries, extended_alphabet, lambda item, category: category == item['letter']) + inspirations_by_alphabet = sort_into_categories(inspirations, extended_alphabet, lambda item, category: category == item['letter']) + developers_by_alphabet = sort_into_categories(developers, extended_alphabet, lambda item, category: category == item['letter']) + + genres = [keyword.capitalize() for keyword in c.recommended_keywords] + games_by_genre = sort_into_categories(entries, genres, lambda item, category: category.lower() in item['Keywords']) + games_by_platform = sort_into_categories(entries, c.valid_platforms, lambda item, category: category in item.get('Platform', []), 'Unspecified') + games_by_language = sort_into_categories(entries, c.known_languages, lambda item, category: category in item['Code language']) # base dictionary base = { 'title': 'OSGL', - 'paths': { - 'css': 'css/bulma.min.css', - 'index': 'index.html', - 'index-games': 'games/index.html', - 'index-developers': 'developers/index.html', - 'index-inspirations': 'inspirations/index.html', - 'index-statistics': 'index-statistics' - }, 'creation-date': datetime.datetime.utcnow() } @@ -114,80 +262,26 @@ def generate(entries, inspirations, developers): template_categorical_index = environment.get_template('categorical_index.jinja') template_list = environment.get_template('list.jinja') + # top level folder + base['url_to'] = partial(url_to, '') + # index.html - index = {'number_games': len(entries)} # TODO only count games + index = {'number_games': len(entries)} template = environment.get_template('index.jinja') write(template.render(index=index), 'index.html') - # generate games pages - games_by_alphabet = sort_by_alphabet(entries, 'Title') - template = environment.get_template('games_for_letter.jinja') - for letter in extended_alphabet: - write(template.render(letter=letter, games=games_by_alphabet[letter]), os.path.join('games', '{}.html'.format(letter.capitalize()))) + # contribute.html + template = environment.get_template('contribute.jinja') + write(template.render(), 'contribute.html') - # generate games index - index = divide_in_columns(games_by_alphabet, 'Title') - index['title'] = 'Games index' - index['categories'] = extended_alphabet - write(template_categorical_index.render(index=index), os.path.join('games', 'index.html')) + # statistics - ## inspirations - inspirations_by_alphabet = sort_by_alphabet(inspirations, 'Name') - - # inspirations index - index = divide_in_columns(inspirations_by_alphabet, 'Name') - index['title'] = 'Inspirations index' - index['categories'] = extended_alphabet - write(template_categorical_index.render(index=index), os.path.join('inspirations', 'index.html')) - - # inspirations single pages - template = environment.get_template('inspirations_for_letter.jinja') - for letter in extended_alphabet: - write(template.render(letter=letter, inspirations=inspirations_by_alphabet[letter]), os.path.join('inspirations', '{}.html'.format(letter.capitalize()))) - - ## developers - - # developers single pages - developers_by_alphabet = sort_by_alphabet(developers, 'Name') - template = environment.get_template('developers_for_letter.jinja') - for letter in extended_alphabet: - write(template.render(letter=letter, developers=developers_by_alphabet[letter]), os.path.join('developers', '{}.html'.format(letter.capitalize()))) - - # developers index - index = divide_in_columns(developers_by_alphabet, 'Name') - index['title'] = 'Developers index' - index['categories'] = extended_alphabet - write(template_categorical_index.render(index=index), os.path.join('developers', 'index.html')) - - ## genres - genres = c.recommended_keywords - games_by_genre = sort_into_categories(entries, genres, lambda item, category: category in item['Keywords'], None) - index = divide_in_columns(games_by_genre, 'Title') - index['title'] = 'Games by genre' - index['categories'] = genres - write(template_categorical_index.render(index=index), os.path.join('games', 'genres.html')) - - ## games by language TODO make categories bold that have a certain amount of entries! - languages = c.known_languages - games_by_language = sort_into_categories(entries, languages, lambda item, category: category in item['Code language'], None) - index = divide_in_columns(games_by_language, 'Title') - index['title'] = 'Games by language' - index['categories'] = languages # it's fine if they get capitalized, because they are already capitalized - write(template_categorical_index.render(index=index), os.path.join('games', 'languages.html')) - - ## games by platform - platforms = c.valid_platforms - games_by_platform = sort_into_categories(entries, platforms, lambda item, category: category in item.get('Platform', []), 'Unspecified') - index = divide_in_columns(games_by_platform, 'Title') - index['title'] = 'Games by platform' - index['categories'] = platforms # TODO (do not capitalize automatically) - write(template_categorical_index.render(index=index), os.path.join('games', 'platforms.html')) - - ## statistics - - # index - template = environment.get_template('statistics_index.jinja') - write(template.render(), os.path.join('statistics', 'index.html')) + # preparation + template = environment.get_template('statistics.jinja') + data = { + 'title': 'Statistics', + 'sections': [] + } # build-systems build_systems = [] @@ -201,12 +295,78 @@ def generate(entries, inspirations, developers): unique_build_systems = [(l, build_systems.count(l)) for l in unique_build_systems] unique_build_systems.sort(key=lambda x: str.casefold(x[0])) # first sort by name unique_build_systems.sort(key=lambda x: -x[1]) # then sort by occurrence (highest occurrence first) - data = { + section = { 'title': 'Build system', 'items': ['{} ({})'.format(*item) for item in unique_build_systems] } - write(template_list.render(data=data), os.path.join('statistics', 'build-systems.html')) + data['sections'].append(section) + write(template.render(data=data), os.path.join('statistics.html')) + # games folder + base['url_to'] = partial(url_to, games_path) + + # generate games pages + template = environment.get_template('games_for_letter.jinja') + for letter in extended_alphabet: + data = { + 'title': 'Games with {}'.format(letter.capitalize()), + 'games': games_by_alphabet[letter] + } + write(template.render(data=data), os.path.join(games_path, '{}.html'.format(letter.capitalize()))) + + # generate games index + index = divide_in_columns(games_by_alphabet, game_index) + index['title'] = 'Alphabetical index' + index['categories'] = extended_alphabet + write(template_categorical_index.render(index=index), os.path.join(games_path, 'index.html')) + + # genres + index = divide_in_columns(games_by_genre, game_index) + index['title'] = 'Games by genre' + index['categories'] = genres + write(template_categorical_index.render(index=index), os.path.join(games_path, 'genres.html')) + + # games by language + index = divide_in_columns(games_by_language, game_index) + index['title'] = 'Games by language' + index['categories'] = c.known_languages + write(template_categorical_index.render(index=index), os.path.join(games_path, 'languages.html')) + + # games by platform + index = divide_in_columns(games_by_platform, game_index) + index['title'] = 'Games by platform' + index['categories'] = c.valid_platforms + ('Unspecified',) + write(template_categorical_index.render(index=index), os.path.join(games_path, 'platforms.html')) + + # inspirations folder + base['url_to'] = partial(url_to, inspirations_path) + + # inspirations + + # inspirations index + index = divide_in_columns(inspirations_by_alphabet, inspiration_index) + index['title'] = 'Inspirations index' + index['categories'] = extended_alphabet + write(template_categorical_index.render(index=index), os.path.join(inspirations_path, 'index.html')) + + # inspirations single pages + template = environment.get_template('inspirations_for_letter.jinja') + for letter in extended_alphabet: + write(template.render(letter=letter, inspirations=inspirations_by_alphabet[letter]), os.path.join(inspirations_path, '{}.html'.format(letter.capitalize()))) + + # developers folder + base['url_to'] = partial(url_to, developers_path) + + # developers single pages + template = environment.get_template('developers_for_letter.jinja') + for letter in extended_alphabet: + write(template.render(letter=letter, developers=developers_by_alphabet[letter]), os.path.join(developers_path, '{}.html'.format(letter.capitalize()))) + + # developers index + index = divide_in_columns(developers_by_alphabet, developer_index) + index['title'] = 'Developers index' + index['categories'] = extended_alphabet + write(template_categorical_index.render(index=index), os.path.join(developers_path, 'index.html')) if __name__ == "__main__": diff --git a/code/html/base.jinja b/code/html/base.jinja index f2548ccf..c4a291b9 100644 --- a/code/html/base.jinja +++ b/code/html/base.jinja @@ -4,22 +4,22 @@