Created
January 20, 2019 21:43
-
-
Save kfsone/a460ca1f79523ac51cf9d685cc7a7d5a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python | |
""" | |
Intended for use from ipython, but you can use via import. | |
iPython usage: | |
import saves | |
csvs = saves.init() | |
%timeit saves.write_csvs(csvs, saves.WritePerValue) | |
%timeit saves.write_csvs(csvs, saves.WritePerLine) | |
%timeit saves.write_csvs(csvs, saves.WriteFormat) | |
Author: Oliver "kfsone" Smith <oliver@kfs.org> Jan 20 2019 | |
""" | |
import logging | |
import re | |
import os | |
# Regex function to match a float value | |
floatMatcher = re.compile(r'^\d+\.\d+$').match | |
class Namespace: | |
""" Decorative: Denotes a class is purely a namespace """ | |
pass | |
class Constant(Namespace): | |
""" Namespace for hard-coded values """ | |
PROFILES_DIR = "C:/Program Files (x86)/Steam/SteamApps/Common/Rise To Ruins/profiles" | |
DEFAULT_PROF = "profile0" | |
SAVES_DIR = "saves/worldMap" | |
class Writer(object): | |
""" | |
Wrapper around a file object that forwards write operations to a | |
callable and forwards data arguments to it. | |
This lets the callable deal with the elements of data in different | |
ways, e.g it lets it marshal them into a single string or it lets | |
it return them in such a way that every value/separator gets its | |
own discrete write() call. | |
""" | |
def __init__(self, file): | |
self.fh = open(os.path.join("tmp", os.path.basename(file)), "w") | |
def write(self, callable, *args, **kwargs): | |
for value in callable(*args, **kwargs): | |
self.fh.write(value) | |
class WriteCallableNamespace(Namespace): | |
""" Namespaces that provide a comment and value callables for Writer.write """ | |
pass | |
class WritePerValue(WriteCallableNamespace): | |
""" | |
Force a 'write' on every value/component of each line, no marshalling. | |
""" | |
@staticmethod | |
def comment(text, _): | |
return ("#", text, "\n") | |
@staticmethod | |
def values(values, raws): | |
csv = [val for pair in zip(values, ','*len(values)) for val in pair] | |
csv[-1] = "\n" | |
return csv | |
class WritePerLine(WriteCallableNamespace): | |
""" | |
Marshall all elements of lines into a single string with one write. | |
""" | |
@staticmethod | |
def comment(text, _): | |
return ("#"+text+"\n",) | |
@staticmethod | |
def values(values, raws): | |
return (",".join(v for v in values) + "\n",) | |
class WriteFormat(WriteCallableNamespace): | |
""" | |
Marshalls each line into strings but then uses printf formatting | |
to build the final string before writing. | |
""" | |
@staticmethod | |
def comment(text, _): | |
return ("#"+text+"\n",) | |
@staticmethod | |
def values(values, raws): | |
return (raws[0] % raws[1],) | |
def yield_save_files(): | |
""" Helper: yields the names of files under *1 folders """ | |
wd = os.path.join(PROFILES_DIR, DEFAULT_PROF, SAVES_DIR) | |
for base, dirs, files in os.walk(wd): | |
# Only yield file names for files that exist under a folder that | |
# ends with '1', this only reads the most recent save. | |
if base.endswith('1'): | |
for file in (os.path.join(base, f) for f in files): | |
yield file | |
def reparse_values(values): | |
""" Convert raw string values into float/int/string % masks """ | |
# fmts and raws will contain the print format and typed value respectively | |
fmts, raws = [], [] | |
for value in values: | |
# Test the float regex against it first | |
if floatMatcher(value): | |
fmt, raw = '%f', float(value) | |
else: | |
# Pythonic: attempt to convert to an int, or fallback to a string. | |
try: | |
raw = int(value) | |
fmt = '%d' | |
except: | |
fmt, raw = '%s', value | |
# Keep lists in sync | |
fmts.append(fmt) | |
raws.append(raw) | |
# Join the formats into a single newlined string and convert the raws into | |
# a tuple for leaness. | |
return ','.join(fmts)+"\n", tuple(raws) | |
def load_csvs(): | |
""" Load all the csvs for a save into a hash map """ | |
csvs = {} | |
for file in yield_csvs(): | |
# skip the .bmp files | |
if file.endswith('.bmp'): | |
continue | |
with open(file, 'rb') as fh: | |
logging.info("Reading " + file) | |
ops = [] # "op"erations, they invoke callables with data. | |
for line in fh: | |
if line.startswith('#'): | |
# lines starting with '#' are comments | |
ops.append(("comment", line.rstrip()[1:], None)) | |
else: | |
# everything else is assumed to be a csv line | |
values = line.rstrip().split(',') | |
ops.append(("values", values, reparse_values(values))) | |
csvs[file] = ops | |
return csvs | |
def write_csvs(csvs, writerClass): | |
""" | |
given a dict(filename: ops), and a class that turns the ops into strings, | |
write every value returned by writerClass members into files. | |
""" | |
for file, ops in csvs.iteritems(): | |
writer = Writer(os.path.basename(file)) | |
for op, data, raw in ops: | |
callable = getattr(writerClass, op) | |
writer.write(callable, data, raw) | |
def init(): | |
return load_csvs() | |
if __name__ == "__main__": | |
logging.basicConfig(level=logging.DEBUG) | |
csvs = init() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment