erynwells.me/scripts/import-nethack-logfile.py

173 lines
5.1 KiB
Python
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# Eryn Wells <eryn@erynwells.me>
'''
Converts a Nethack logfile (or record file) to JSON for easier parsing by my
website's templating engine.
See https://nethackwiki.com/wiki/Logfile for information about the format of these
logfiles.
'''
import argparse
import datetime
import json
from locale import normalize
import os.path
import subprocess
import sys
DUNGEONS = {
0: 'The Dungeons of Doom',
1: 'Gehennom',
2: 'The Gnomish Mines',
3: 'The Quest',
4: 'Sokoban',
5: 'Fort Ludios',
6: 'Vlads Tower',
7: 'The Elemental Planes',
}
# The "dungeon level" field is normally positive, but negatives indicate one of these
# levels.
SPECIAL_DUNGEON_LEVELS = {
-5: 'Astral Plane',
-4: 'Plane of Water',
-3: 'Plane of Fire',
-2: 'Plane of Air',
-1: 'Plane of Earth',
}
RACES = {
'Elf': 'Elf',
'Gno': 'Gnome',
'Hum': 'Human',
'Dwa': 'Dwarf',
}
ROLES = {
'Arc': 'Archaeologist',
'Kni': 'Knight',
'Mon': 'Monk',
'Pri': 'Priest',
'Ran': 'Ranger',
'Rog': 'Rogue',
'Sam': 'Samurai',
'Val': 'Valkyrie',
}
GENDERS = {
'Fem': 'Female',
'Mal': 'Male',
}
ALIGNMENTS = {
'Law': 'Lawful',
'Neu': 'Neutral',
'Cha': 'Chaotic',
}
def parse_args(argv, *a, **kw):
parser = argparse.ArgumentParser(*a, **kw)
parser.add_argument('-o', '--output', help='Path to the output file')
parser.add_argument('logfile', help='Path to the Nethack log file to convert')
args = parser.parse_args(argv)
return args
def main(argv):
args = parse_args(argv[1:], prog=argv[0])
if not os.path.isfile(args.logfile):
print('Given path is not a real file!', file=sys.stderr)
return -1
hostname = subprocess.check_output(['hostname', '-s']).decode('ascii').strip()
records = []
with open(args.logfile) as logfile:
for record in logfile:
fields = record.split(maxsplit=15)
dungeon_number = int(fields[2])
dungeon_name = DUNGEONS[dungeon_number]
dungeon_level = int(fields[3])
if dungeon_level > 0:
dungeon_level_descriptive = f"Level {dungeon_level}"
else:
dungeon_level_descriptive = SPECIAL_DUNGEON_LEVELS[dungeon_level]
max_dungeon_level = int(fields[4])
if max_dungeon_level > 0:
max_dungeon_level_descriptive = f"Level {max_dungeon_level}"
else:
max_dungeon_level_descriptive = SPECIAL_DUNGEON_LEVELS[max_dungeon_level]
start_date = datetime.datetime.strptime(fields[9], '%Y%m%d').strftime('%Y-%m-%d')
end_date = datetime.datetime.strptime(fields[8], '%Y%m%d').strftime('%Y-%m-%d')
name, cause_of_death = (s.strip() for s in fields[15].split(',', maxsplit=1))
role = fields[11]
race = fields[12]
gender = fields[13]
alignment = fields[14]
records.append({
'score': int(fields[1]),
'dungeon': {
'n': int(fields[2]),
'name': dungeon_name,
'level': {'n': dungeon_level, 'descriptive': dungeon_level_descriptive},
'max_level': {'n': max_dungeon_level, 'descriptive': max_dungeon_level_descriptive},
},
'end_date': end_date,
'start_date': start_date,
'character': {
'name': name,
'descriptor': f'{name}-{role}-{race}-{gender}-{alignment}',
'hp': {'n': int(fields[5]), 'max': int(fields[6])},
'role': {'short': role, 'descriptive': ROLES[role]},
'race': {'short': race, 'descriptive': RACES[race]},
'gender': {'short': gender, 'descriptive': GENDERS[gender]},
'alignment': {'short': alignment, 'descriptive': ALIGNMENTS[alignment]},
},
'death': {'n': int(fields[7]), 'cause': cause_of_death},
'system': {
'hostname': hostname,
'user_id': int(fields[10]),
'nethack_version': fields[0],
}
})
output_object = {
'generated': datetime.datetime.now().isoformat(),
'logfile': records,
}
logfile_has_changed = False
output_path = args.output
output_file = None
if output_path and output_path != '-':
with open(output_path, 'r') as existing_logfile:
existing_logfile_object = json.load(existing_logfile)
logfile_has_changed = existing_logfile_object.get('logfile', {}) != records
if logfile_has_changed:
output_file = open(output_path, 'w')
else:
output_file = sys.stdout
if output_file:
json.dump(output_object, output_file, indent=2)
output_file.write('\n')
output_file.close()
else:
print('No changes to logfile')
return 0
if __name__ == '__main__':
import sys
result = main(sys.argv)
sys.exit(0 if not result else result)