"""
Generic configuration file output
"""
import os, os.path, tempfile, shutil
class Item (object) :
"""
An object that can be represented as a series of lines of text.
The structure of these objects/lines is not defined, but as a convenience, a method for prepending lines of
comments is provided.
"""
# the prefix char for comments, must be defined
COMMENT_PREFIX = None
@classmethod
def filter_comments (self, comments) :
"""
Return the filtered list of comments
"""
return [comment for comment in comments if comment]
def __init__ (self, comments=()) :
"""
Initialize with the given list of comments, which will be filtered before use.
"""
# init the comments list
self.comments = self.filter_comments(comments)
def add_comment (self, comment) :
"""
Add the given comment
"""
self.comments += self.filter_comments([comment])
def iter_comments (self) :
"""
Formats comments for output
"""
for comment in self.comments :
yield "%s %s" % (self.COMMENT_PREFIX, comment)
def iter_lines (self, indent='\t') :
"""
Yield a series of lines to be output in the file
"""
abstract
def render_lines (self, encoding=None, newline='\n', **opts) :
"""
Render lines as a series of objects, possibly encoded.
"""
for line in self.iter_lines(**opts) :
if encoding :
line = line.encode(encoding)
elif encoding is False :
line = unicode(line)
yield line + newline
def render_unicode (self, **opts) :
"""
Render lines as a single unicode object
"""
return u''.join(self.render_lines(encoding=False, **opts))
def render_str (self, encoding='utf-8', **opts) :
"""
Render lines as a single str object
"""
return ''.join(self.render_lines(encoding=encoding, **opts))
def render_out (self, file, encoding='utf-8', **opts) :
"""
Render lines and write out to given file object
"""
for line in self.render_lines(encoding=encoding, **opts) :
file.write(line)
class Contents (Item) :
"""
A series of Items
"""
def __init__ (self, *items) :
self.items = items
def add_item (self, item) :
self.items.append(item)
def iter_lines (self, **opts) :
for item in self.items :
for line in item.iter_lines(**opts) :
yield line
class File (object) :
"""
A configuration file on the filesystem.
Configuration files can be written out with some given contents.
XXX: better logic for backup file
"""
def __init__ (self, name, path, backup_suffix='.bak', mode=0644) :
"""
Initialize the config file, but don't open it yet
@param name the human-readable friendly name for the config file
@param path the full filesystem path for the file
@param backup_suffix rename the old file by adding this suffix when replacing it
@param mode the permission bits for the new file
"""
self.name = name
self.path = path
self.backup_suffix = backup_suffix
self.mode = mode
def write (self, data) :
"""
Write out a new config file with the given Item data using the following procedure:
* lock the real file
* open a new temporary file, and write out the objects into that, closing it
* move the real file out of the way
* move the temporary file into the real file's place
* unlock the real file
"""
# XXX: how to aquire the lock?
# XXX: this needs checking over
# open the new temporary file
# XXX: ensure OS fd is closed
tmp_fd, tmp_path = tempfile.mkstemp(prefix=self.name)
# ...as a file
tmp_file = os.fdopen(tmp_fd, "w")
# fix the permissions
os.chmod(tmp_path, self.mode)
# write the Item out into the file
data.render_out(tmp_file)
# close it
tmp_file.close()
# move the old file out of the way
if os.path.exists(self.path) :
# XXX: use shutil.move?
os.rename(self.path, self.path + self.backup_suffix)
# move the new file in
shutil.move(tmp_path, self.path)