pvl/config/file.py
author Tero Marttila <terom@fixme.fi>
Sun, 12 Jul 2009 02:14:08 +0300
changeset 8 46d36bc33086
parent 7 pvl/config/conf.py@0f9cae2d7147
permissions -rw-r--r--
rename conf to file and start refactoring
"""
    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)