Skip to content

Instantly share code, notes, and snippets.

@mfm24
Created June 29, 2015 20:23
Show Gist options
  • Save mfm24/d4e8ea3afe2140374bb5 to your computer and use it in GitHub Desktop.
Save mfm24/d4e8ea3afe2140374bb5 to your computer and use it in GitHub Desktop.
# config_sectioner
#
# Treats a text file as a python file object, only
# inserting, adding or removing text in a marked off section.
# If the section doesn't exist it is appended to the file.
# Include information about the last date the file was written
# and by which programs. All blocks have a unique key, so that
# a file can contain multiple ConfigSections with different keys.
from __future__ import division, print_function
from cStringIO import StringIO
import time
import logging
__PROGRAM__ = "ConfigSection"
__VERSION__ = "0.01"
class ConfigSection(object):
def __init__(self, key, inner_stream, append=False, line_comment='#'):
self.key = key
self.stream = inner_stream
if append:
self.mid_io = StringIO(self._get_mid_data())
else:
self.mid_io = StringIO()
self.mid_io.seek(0)
self.section_text = ""
self.line_comment = line_comment
def read(self, n=-1):
# NB docs say:
# When size is omitted or negative, the entire
# contents of the file will be read and returned
return self.mid_io.read(n)
def write(self, data):
self.mid_io.write(data)
def seek(self, pos):
self.mid_io.seek(pos)
def tell(self):
self.mid_io.tell()
def flush(self):
self._write_mid_data(self.mid_io)
# we'll flush if we can
try:
self.stream.flush()
except AttributeError:
pass
def get_start_header(self):
return "{} ----- BEGIN {} -----".format(self.line_comment, self.key)
def get_end_header(self):
return "{} ----- END {} -----".format(self.line_comment, self.key)
def get_banner(self):
dat = """#
# Auto-generated section.
# This section of the file generated by {PROGRAM} v{VERSION} on {TIME}.
# WARNING: DO NOT EDIT ANY TEXT BETWEEN THE "BEGIN {KEY}" LINE ABOVE
# AND THE "END {KEY}" LINE BELOW! These lines have been automatically
# generated by a tool and any text contained between these lines may be
# regenerated or deleted at any time!
"""
t = time.asctime()
ret = "\n".join(
l.strip().replace('#', self.line_comment).format(
PROGRAM=__PROGRAM__,
VERSION=__VERSION__,
TIME=t,
KEY=self.key)
for l in dat.splitlines()
)
return ret
@staticmethod
def split(stream, delim, maxsplit=None):
""" Returns a list of StringIOs splitting stream with delim.
If maxplit is specified, we split that many times."""
ret = [StringIO()]
for d in stream:
i = d.find(delim)
if i >= 0 and (maxsplit is None or len(ret) <= maxsplit):
ret[-1].write(d[:i])
ret.append(StringIO())
ret[-1].write(d[i+len(delim):])
else:
ret[-1].write(d)
return ret
def _write_mid_data(self, mid_data):
"""We search for our section delimiters in the file, and
overwrite them with the start header, the banner, the wanted text,
then the end banner, followed by the rest of the file."""
start_header = self.get_start_header()
end_header = self.get_end_header()
self.stream.seek(0)
pre_mid = self.split(self.stream, start_header)
try:
pre, mid = pre_mid
except ValueError:
pre = pre_mid[0]
# add line ffeds separating the block:
start_header = "\n" + start_header
end_header = end_header + "\n"
post = StringIO()
else:
mid.seek(0)
mid_post = self.split(mid, end_header)
try:
mid, post = mid_post
except ValueError:
# no end block found. In this case we err on side of caution
# and leave everything intact
logging.warn(
"No end block found! Keeping all text after start block!")
post = mid_post[0]
self.stream.seek(0)
for s in [pre,
StringIO(start_header+"\n"+self.get_banner()),
mid_data,
StringIO("\n"+end_header),
post]:
s.seek(0)
self.stream.write(s.read())
def _get_mid_data(self):
s = self.get_start_header()
e = self.get_end_header()
self.stream.seek(0)
pre, mid = self.split(self.stream, s)
mid.seek(0)
mid, post = self.split(mid, e)
mid.seek(0)
return "\n".join(x for x in mid if not x.startswith(self.line_comment))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment