Skip to content

Instantly share code, notes, and snippets.

@kfsone
Created January 20, 2019 21:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kfsone/a460ca1f79523ac51cf9d685cc7a7d5a to your computer and use it in GitHub Desktop.
Save kfsone/a460ca1f79523ac51cf9d685cc7a7d5a to your computer and use it in GitHub Desktop.
#! /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