maintenance

This commit is contained in:
Trilarion
2019-12-05 17:27:48 +01:00
parent 7021becd32
commit 29355829e6
62 changed files with 426 additions and 375 deletions

View File

@@ -140,7 +140,6 @@
"https://github.com/Anthonymcqueen21/Pygame---Alien-Invasion.git",
"https://github.com/Anuken/Mindustry.git",
"https://github.com/Arantis/Meridian59_112.git",
"https://github.com/Argentum-Online/Argentum-Online.git",
"https://github.com/ArmageddonGames/ZeldaClassic.git",
"https://github.com/ArmagetronAd/armagetronad.git",
"https://github.com/ArnoAnsems/CatacombGL.git",
@@ -351,6 +350,7 @@
"https://github.com/SteveSmith16384/TumblyTowers.git",
"https://github.com/SupSuper/OpenXcom.git",
"https://github.com/SuperTux/supertux.git",
"https://github.com/SuperV1234/SSVOpenHexagon.git",
"https://github.com/TASVideos/desmume.git",
"https://github.com/TES3MP/openmw-tes3mp.git",
"https://github.com/TIHan/FQuake3.git",
@@ -433,6 +433,9 @@
"https://github.com/anttisalonen/freekick3.git",
"https://github.com/anttisalonen/kingdoms.git",
"https://github.com/anura-engine/anura.git",
"https://github.com/ao-libre/ao-cliente.git",
"https://github.com/ao-libre/ao-server.git",
"https://github.com/ao-libre/ao-worldeditor.git",
"https://github.com/aperture-software/colditz-escape.git",
"https://github.com/apsillers/Taggem.git",
"https://github.com/apsillers/lords-of-the-fey.git",
@@ -982,6 +985,7 @@
"https://github.com/zuzuf/TA3D.git",
"https://gitlab.axiodl.com/AxioDL/urde.git",
"https://gitlab.com/Dringgstein/Commander-Genius.git",
"https://gitlab.com/EugeneLoza/Project-Helena.git",
"https://gitlab.com/KilgoreTroutMaskReplicant/1oom.git",
"https://gitlab.com/LibreGames/freesiege.git",
"https://gitlab.com/SuperTuxParty/SuperTuxParty.git",

View File

@@ -13,6 +13,7 @@ http://game-editor.com/Main_Page
http://giderosmobile.com/
http://haxepunk.com/
http://hcsoftware.sourceforge.net/jason-rohrer/ (various games there)
http://hgm.nubati.net/
http://icculus.org/
http://icculus.org/asciiroth/
http://icculus.org/avp/
@@ -190,7 +191,6 @@ https://github.com/AdaDoom3/AdaDoom3
https://github.com/AdamsLair/duality
https://github.com/Alzter/TuxBuilder
https://github.com/amerkoleci/Vortice.Windows
https://github.com/ao-libre/ao-cliente
https://github.com/arturkot/the-house-game
https://github.com/AtomicGameEngine/AtomicGameEngine
https://github.com/atphalix/nexuiz
@@ -222,6 +222,7 @@ https://github.com/Donerkebap13/DonerComponents
https://github.com/Drasky-Vanderhoff/CommonDrops
https://github.com/EaW-Team/equestria_dev
https://github.com/ec-/Quake3e
https://github.com/EliFUT/android
https://github.com/elishacloud/Silent-Hill-2-Enhancements
https://github.com/endlesstravel/Love2dCS
https://github.com/enduro2d/enduro2d

View File

@@ -55,7 +55,7 @@ def download_lgw_content():
for game in games:
print(game[1])
url = base_url + game[0]
destination_file = os.path.join(destination_path, osg.canonical_game_name(game[0][1:]) + '.html')
destination_file = os.path.join(destination_path, osg.canonical_entry_name(game[0][1:]) + '.html')
text = requests.get(url).text
utils.write_text(destination_file, text)

View File

@@ -140,7 +140,7 @@ if __name__ == "__main__":
print('similar names')
for lgw_name in lgw_names:
for our_name in our_names:
if osg.game_name_similarity(lgw_name, our_name) > similarity_threshold:
if osg.name_similarity(lgw_name, our_name) > similarity_threshold:
print('{} - {}'.format(lgw_name, our_name))
newly_created_entries = 0
@@ -198,7 +198,7 @@ if __name__ == "__main__":
# determine file name
print('create new entry for {}'.format(lgw_name))
file_name = osg.canonical_game_name(lgw_name) + '.md'
file_name = osg.canonical_entry_name(lgw_name) + '.md'
target_file = os.path.join(constants.entries_path, file_name)
if os.path.isfile(target_file):
print('warning: file {} already existing, save under slightly different name'.format(file_name))

View File

@@ -30,7 +30,7 @@ def update_readme_and_tocs(infos):
"""
print('update readme and toc files')
# delete content of toc path
# completely delete content of toc path
for file in os.listdir(c.tocs_path):
os.remove(os.path.join(c.tocs_path, file))
@@ -49,11 +49,31 @@ def update_readme_and_tocs(infos):
start = matches[0]
end = matches[2]
# create all toc
title = 'All'
file = '_all.md'
tocs_text = '**[{} entries](entries/tocs/{}#{})** ({})\n'.format(title, file, title, len(infos))
create_toc(title, file, infos)
tocs_text = ''
# split infos
infos_games, infos_tools, infos_frameworks, infos_libraries = osg.split_infos(infos)
# create games, tools, frameworks, libraries tocs
title = 'Games'
file = '_games.md'
tocs_text += '**[{}](entries/tocs/{}#{})** ({}) - '.format(title, file, title, len(infos_games))
create_toc(title, file, infos_games)
title = 'Tools'
file = '_tools.md'
tocs_text += '**[{}](entries/tocs/{}#{})** ({}) - '.format(title, file, title, len(infos_tools))
create_toc(title, file, infos_tools)
title = 'Frameworks'
file = '_frameworks.md'
tocs_text += '**[{}](entries/tocs/{}#{})** ({}) - '.format(title, file, title, len(infos_frameworks))
create_toc(title, file, infos_frameworks)
title = 'Libraries'
file = '_libraries.md'
tocs_text += '**[{}](entries/tocs/{}#{})** ({})\n'.format(title, file, title, len(infos_libraries))
create_toc(title, file, infos_libraries)
# create by category
categories_text = []
@@ -116,6 +136,8 @@ def check_validity_external_links():
from time to time.
"""
# TODO check if links are occurring in multiple entries, first go through all entries and find all links, then check links for multiple entries, then check links, follow redirects
print("check external links (can take a while)")
# regex for finding urls (can be in <> or in ]() or after a whitespace
@@ -191,6 +213,7 @@ def fix_entries():
keyword_synonyms = {'RTS': ('real time', 'strategy'), 'realtime': 'real time'}
# TODO also sort other fields, only read once and then do all, move to separate file
# example Javascript to JavaScript and then add whenever the known languages check hits
print('fix entries')
@@ -410,8 +433,10 @@ def update_statistics(infos):
for info in infos:
if field in info:
keywords.extend(info[field])
# ignore those starting with "inspired by"
keywords = [x for x in keywords if not x.startswith('inspired by ')]
# reduce those starting with "inspired by"
keywords = [x if not x.startswith('inspired by') else 'inspired' for x in keywords]
# reduce those starting with "multiplayer"
keywords = [x if not x.startswith('multiplayer') else 'multiplayer' for x in keywords]
unique_keywords = set(keywords)
unique_keywords = [(l, keywords.count(l) / len(keywords)) for l in unique_keywords]

View File

@@ -381,7 +381,7 @@ if __name__ == "__main__":
# determine file name
print('create new entry for {}'.format(osgc_name))
file_name = osg.canonical_game_name(osgc_name) + '.md'
file_name = osg.canonical_entry_name(osgc_name) + '.md'
target_file = os.path.join(constants.entries_path, file_name)
if os.path.isfile(target_file):
print('warning: file {} already existing, save under slightly different name'.format(file_name))

View File

@@ -12,14 +12,29 @@ valid_fields = ('Home', 'Media', 'State', 'Play', 'Download', 'Platform', 'Keywo
'Code license', 'Code dependencies', 'Assets license', 'Build system', 'Build instructions')
valid_platforms = ('Windows', 'Linux', 'macOS', 'Android', 'iOS', 'Web')
recommended_keywords = ('action', 'arcade', 'adventure', 'visual novel', 'sports', 'platform', 'puzzle', 'role playing', 'simulation', 'strategy', 'card game', 'board game', 'music', 'educational', 'tool', 'game engine', 'framework', 'library', 'remake')
known_languages = ('AGS Script', 'ActionScript', 'Ada', 'AngelScript', 'Assembly', 'Basic', 'Blender Script', 'BlitzMax', 'C', 'C#', 'C++', 'Clojure', 'CoffeeScript', 'ColdFusion', 'D', 'DM', 'Dart', 'Dia', 'Elm', 'Emacs Lisp', 'F#', 'GDScript', 'Game Maker Script', 'Go', 'Groovy', 'Haskell', 'Haxe', 'Io', 'Java', 'JavaScript', 'Kotlin', 'Lisp', 'Lua', 'MegaGlest Script', 'MoonScript', 'None', 'OCaml', 'Objective-C', 'PHP', 'Pascal', 'Perl', 'Python', 'QuakeC', 'R', "Ren'py", 'Ruby', 'Rust', 'Scala', 'Scheme', 'Script', 'Shell', 'Swift', 'TorqueScript', 'TypeScript', 'Vala', 'Visual Basic', 'XUL', 'ZenScript', 'ooc')
known_licenses = ('2-clause BSD', '3-clause BSD', 'AFL-3.0', 'AGPL-3.0', 'Apache-2.0', 'Artistic License-1.0', 'Artistic License-2.0', 'Boost-1.0', 'CC-BY-NC-3.0', 'CC-BY-NC-SA-2.0', 'CC-BY-SA-3.0', 'CC-BY-SA-4.0', 'CC0', 'Custom', 'EPL-2.0', 'GPL-2.0', 'GPL-3.0', 'IJG', 'ISC', 'Java Research License', 'LGPL-2.0', 'LGPL-2.1', 'LGPL-3.0', 'MAME', 'MIT', 'MPL-1.1', 'MPL-2.0', 'MS-PL', 'MS-RL', 'NetHack General Public License', 'None', 'Proprietary', 'Public domain', 'SWIG license', 'Unlicense', 'WTFPL', 'wxWindows license', 'zlib')
known_multiplayer_modes = ('competitive', 'co-op', 'hotseat', 'LAN', 'local', 'massive', 'matchmaking', 'online', 'split-screen')
regex_sanitize_name = re.compile(r"[^A-Za-z 0-9-+]+")
regex_sanitize_name_space_eater = re.compile(r" +")
def game_name_similarity(a, b):
def name_similarity(a, b):
return SequenceMatcher(None, str.casefold(a), str.casefold(b)).ratio()
def split_infos(infos):
"""
Split into games, tools, frameworks, libraries
"""
games = [x for x in infos if not any([y in x['keywords'] for y in ('tool', 'framework', 'library')])]
tools = [x for x in infos if 'tool' in x['keywords']]
frameworks = [x for x in infos if 'framework' in x['keywords']]
libraries = [x for x in infos if 'library' in x['keywords']]
return games, tools, frameworks, libraries
def entry_iterator():
"""
@@ -43,7 +58,7 @@ def entry_iterator():
yield entry, entry_path, content
def canonical_game_name(name):
def canonical_entry_name(name):
"""
Derives a canonical game name from an actual game name (suitable for file names, ...)
"""
@@ -59,7 +74,9 @@ def canonical_game_name(name):
def parse_entry(content):
"""
Returns a dictionary of the features of the content
Returns a dictionary of the features of the content.
Raises errors when a major error in the structure is expected, prints a warning for minor errors.
"""
info = {}
@@ -67,14 +84,14 @@ def parse_entry(content):
# read name
regex = re.compile(r"^# (.*)") # start of content, starting with "# " and then everything until the end of line
matches = regex.findall(content)
if len(matches) != 1 or not matches[0]:
if len(matches) != 1 or not matches[0]: # name must be there
raise RuntimeError('Name not found in entry "{}" : {}'.format(content, matches))
info['name'] = matches[0]
# read description
regex = re.compile(r"^.*\n\n_(.*)_\n") # third line from top, everything between underscores
matches = regex.findall(content)
if len(matches) != 1 or not matches[0]:
if len(matches) != 1 or not matches[0]: # description must be there
raise RuntimeError('Description not found in entry "{}"'.format(content))
info['description'] = matches[0]
@@ -84,7 +101,7 @@ def parse_entry(content):
# check that essential fields are there
for field in essential_fields:
if field not in fields:
if field not in fields: # essential fields must be there
raise RuntimeError('Essential field "{}" missing in entry "{}"'.format(field, info['name']))
# check that all fields are valid fields and are existing in that order
@@ -92,15 +109,14 @@ def parse_entry(content):
for field in fields:
while index < len(valid_fields) and field != valid_fields[index]:
index += 1
if index == len(valid_fields):
if index == len(valid_fields): # must be valid fields and must be in the right order
raise RuntimeError('Field "{}" in entry "{}" either misspelled or in wrong order'.format(field, info['name']))
# iterate over found fields
for field in fields:
regex = re.compile(r"- {}: (.*)".format(field))
matches = regex.findall(content)
if len(matches) != 1:
# every field should only be present once
if len(matches) != 1: # every field must be present only once
raise RuntimeError('Field "{}" in entry "{}" exist multiple times.'.format(field, info['name']))
v = matches[0]
@@ -129,17 +145,17 @@ def parse_entry(content):
# store in info
info[field.lower()] = v
# check that essential fields made it through
# check again that essential fields made it through
for field in ('home', 'state', 'keywords', 'code language', 'code license'):
if field not in info:
raise RuntimeError('Essential field "{}" missing or empty in entry "{}"'.format(field, info['name']))
if field not in info: # essential fields must still be inside
raise RuntimeError('Essential field "{}" empty in entry "{}"'.format(field, info['name']))
# now checks on the content of fields
# name should not have spaces at the begin or end
v = info['name']
if len(v) != len(v.strip()):
raise RuntimeError('No leading or trailing spaces in the entry name, "{}"'.format(info['name']))
if len(v) != len(v.strip()): # warning about that
print('Warning: No leading or trailing spaces in the entry name, "{}"'.format(info['name']))
# state (essential field) must contain either beta or mature but not both, but at least one
v = info['state']
@@ -165,11 +181,14 @@ def parse_entry(content):
if ' ' in url:
raise RuntimeError('URL "{}" in entry "{}" contains a space'.format(url, info['name']))
# github repositories should end on .git
# github/gitlab repositories should end on .git and should start with https
if 'code repository' in info:
for repo in info['code repository']:
if repo.startswith('https://github.com/') and not repo.endswith('.git'):
raise RuntimeError('Github repo {} in entry "{}" should end on .git.'.format(repo, info['name']))
if any((x in repo for x in ('github', 'gitlab', 'git.tuxfamily', 'git.savannah'))):
if not repo.startswith('https://'):
print('Warning: Repo {} in entry "{}" should start with https://'.format(repo, info['name']))
if not repo.endswith('.git'):
print('Warning: Repo {} in entry "{}" should end on .git.'.format(repo, info['name']))
# check that all platform tags are valid tags and are existing in that order
if 'platform' in info:
@@ -177,7 +196,7 @@ def parse_entry(content):
for platform in info['platform']:
while index < len(valid_platforms) and platform != valid_platforms[index]:
index += 1
if index == len(valid_platforms):
if index == len(valid_platforms): # must be valid platforms and must be in that order
raise RuntimeError('Platform tag "{}" in entry "{}" either misspelled or in wrong order'.format(platform, info['name']))
# there must be at least one keyword
@@ -190,9 +209,21 @@ def parse_entry(content):
if recommended_keyword in info['keywords']:
fail = False
break
if fail:
if fail: # must be at least one recommended keyword
raise RuntimeError('Entry "{}" contains no recommended keyword'.format(info['name']))
# languages should be known
languages = info['code language']
for language in languages:
if language not in known_languages:
print('Warning: Language {} in entry "{}" is not a known language. Misspelled or new?'.format(language, info['name']))
# licenses should be known
licenses = info['code license']
for license in licenses:
if license not in known_licenses:
print('Warning: License {} in entry "{}" is not a known license. Misspelled or new?'.format(license, info['name']))
return info
@@ -216,7 +247,7 @@ def assemble_infos():
info['file'] = entry
# check canonical file name
canonical_file_name = canonical_game_name(info['name']) + '.md'
canonical_file_name = canonical_entry_name(info['name']) + '.md'
# we also allow -X with X =2..9 as possible extension (because of duplicate canonical file names)
if canonical_file_name != entry and canonical_file_name != entry[:-5] + '.md':
print('file {} should be {}'.format(entry, canonical_file_name))