maintenance
This commit is contained in:
@@ -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",
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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))
|
||||
|
@@ -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]
|
||||
|
@@ -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))
|
||||
|
@@ -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))
|
||||
|
Reference in New Issue
Block a user