2022-04-13 08:34:34 -07:00
|
|
|
|
#!/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
|
2022-04-24 17:08:21 -07:00
|
|
|
|
from locale import normalize
|
2022-04-13 08:34:34 -07:00
|
|
|
|
import os.path
|
2022-04-16 18:20:59 +00:00
|
|
|
|
import subprocess
|
2022-04-13 08:34:34 -07:00
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
DUNGEONS = {
|
|
|
|
|
0: 'The Dungeons of Doom',
|
|
|
|
|
1: 'Gehennom',
|
|
|
|
|
2: 'The Gnomish Mines',
|
|
|
|
|
3: 'The Quest',
|
|
|
|
|
4: 'Sokoban',
|
|
|
|
|
5: 'Fort Ludios',
|
2022-12-05 08:14:39 -08:00
|
|
|
|
6: 'Vlad’s Tower',
|
2022-04-13 08:34:34 -07:00
|
|
|
|
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 = {
|
2022-04-13 15:37:34 +00:00
|
|
|
|
'Elf': 'Elf',
|
|
|
|
|
'Gno': 'Gnome',
|
2022-04-13 08:34:34 -07:00
|
|
|
|
'Hum': 'Human',
|
2022-11-28 16:48:58 -08:00
|
|
|
|
'Dwa': 'Dwarf',
|
2022-04-13 08:34:34 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ROLES = {
|
2022-11-20 10:01:07 -08:00
|
|
|
|
'Arc': 'Archaeologist',
|
|
|
|
|
'Kni': 'Knight',
|
2022-04-13 08:34:34 -07:00
|
|
|
|
'Mon': 'Monk',
|
2022-04-13 15:37:34 +00:00
|
|
|
|
'Pri': 'Priest',
|
|
|
|
|
'Ran': 'Ranger',
|
2022-04-13 08:34:34 -07:00
|
|
|
|
'Rog': 'Rogue',
|
|
|
|
|
'Sam': 'Samurai',
|
2022-11-20 10:01:07 -08:00
|
|
|
|
'Val': 'Valkyrie',
|
2022-04-13 08:34:34 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GENDERS = {
|
|
|
|
|
'Fem': 'Female',
|
2022-11-24 08:25:30 -05:00
|
|
|
|
'Mal': 'Male',
|
2022-04-13 08:34:34 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ALIGNMENTS = {
|
|
|
|
|
'Law': 'Lawful',
|
|
|
|
|
'Neu': 'Neutral',
|
|
|
|
|
'Cha': 'Chaotic',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def parse_args(argv, *a, **kw):
|
|
|
|
|
parser = argparse.ArgumentParser(*a, **kw)
|
2022-11-20 10:01:07 -08:00
|
|
|
|
parser.add_argument('-o', '--output', help='Path to the output file')
|
2022-04-13 08:34:34 -07:00
|
|
|
|
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
|
|
|
|
|
|
2022-04-16 18:20:59 +00:00
|
|
|
|
hostname = subprocess.check_output(['hostname', '-s']).decode('ascii').strip()
|
|
|
|
|
|
2022-04-13 08:34:34 -07:00
|
|
|
|
records = []
|
|
|
|
|
with open(args.logfile) as logfile:
|
2022-04-24 17:08:21 -07:00
|
|
|
|
for record in logfile:
|
2022-04-13 08:34:34 -07:00
|
|
|
|
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]
|
|
|
|
|
|
2022-12-05 08:14:39 -08:00
|
|
|
|
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]
|
|
|
|
|
|
2022-04-13 08:34:34 -07:00
|
|
|
|
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')
|
|
|
|
|
|
2022-04-16 18:20:59 +00:00
|
|
|
|
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]
|
2022-04-13 08:34:34 -07:00
|
|
|
|
|
|
|
|
|
records.append({
|
|
|
|
|
'score': int(fields[1]),
|
2022-04-16 18:20:59 +00:00
|
|
|
|
'dungeon': {
|
|
|
|
|
'n': int(fields[2]),
|
|
|
|
|
'name': dungeon_name,
|
|
|
|
|
'level': {'n': dungeon_level, 'descriptive': dungeon_level_descriptive},
|
2022-12-05 08:14:39 -08:00
|
|
|
|
'max_level': {'n': max_dungeon_level, 'descriptive': max_dungeon_level_descriptive},
|
2022-04-16 18:20:59 +00:00
|
|
|
|
},
|
2022-04-13 08:34:34 -07:00
|
|
|
|
'end_date': end_date,
|
|
|
|
|
'start_date': start_date,
|
2022-04-16 18:20:59 +00:00
|
|
|
|
'character': {
|
|
|
|
|
'name': name,
|
2022-04-24 17:08:21 -07:00
|
|
|
|
'descriptor': f'{name}-{role}-{race}-{gender}-{alignment}',
|
2022-04-16 18:20:59 +00:00
|
|
|
|
'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],
|
|
|
|
|
}
|
2022-04-13 08:34:34 -07:00
|
|
|
|
})
|
|
|
|
|
|
2022-04-24 17:08:21 -07:00
|
|
|
|
output_object = {
|
|
|
|
|
'generated': datetime.datetime.now().isoformat(),
|
|
|
|
|
'logfile': records,
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-20 10:01:07 -08:00
|
|
|
|
logfile_has_changed = False
|
2022-04-24 17:08:21 -07:00
|
|
|
|
output_path = args.output
|
2022-11-20 10:01:07 -08:00
|
|
|
|
output_file = None
|
|
|
|
|
|
2022-04-24 17:08:21 -07:00
|
|
|
|
if output_path and output_path != '-':
|
2022-11-20 10:01:07 -08:00
|
|
|
|
with open(output_path, 'r') as existing_logfile:
|
|
|
|
|
existing_logfile_object = json.load(existing_logfile)
|
2022-11-24 08:25:30 -05:00
|
|
|
|
logfile_has_changed = existing_logfile_object.get('logfile', {}) != records
|
2022-11-20 10:01:07 -08:00
|
|
|
|
|
|
|
|
|
if logfile_has_changed:
|
|
|
|
|
output_file = open(output_path, 'w')
|
2022-04-24 17:08:21 -07:00
|
|
|
|
else:
|
|
|
|
|
output_file = sys.stdout
|
|
|
|
|
|
2022-11-20 10:01:07 -08:00
|
|
|
|
if output_file:
|
|
|
|
|
json.dump(output_object, output_file, indent=2)
|
|
|
|
|
output_file.write('\n')
|
|
|
|
|
output_file.close()
|
|
|
|
|
else:
|
|
|
|
|
print('No changes to logfile')
|
2022-04-24 17:08:21 -07:00
|
|
|
|
|
|
|
|
|
return 0
|
2022-04-13 08:34:34 -07:00
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
import sys
|
|
|
|
|
result = main(sys.argv)
|
|
|
|
|
sys.exit(0 if not result else result)
|