Skip to content

Instantly share code, notes, and snippets.

@Cilyan
Created December 20, 2013 11:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Cilyan/8053594 to your computer and use it in GitHub Desktop.
Save Cilyan/8053594 to your computer and use it in GitHub Desktop.
A class to insert content between two tags in a file (draft)
#
# Copyright 2013 Cilyan Olowen <gaknar@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of the nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import random
import os
import os.path
import sys
class FileInsert:
"""
File-like interface that writes its content between two tags inside a
template file. This works by copying the content of input to output
until the start tag is met. Then the specific data is written. When the
:py:func:`~FileInsert.close` function is called, the end tag is searched
and from there, the rest of the file is copied to the output.
.. sourcecode: python
fout = FileInsert.open(template_path, result_path)
fout.write(some_data)
fout.close()
You need to call :py:func:`~FileInsert.close`, or the content of the
file after the end tag will not be copied to the output.
The object is also a context manager. If ``outputfile`` is ``None`` or
not specified, the input file is overwritten. The class tries its best
not to lose any data. The inputfile will be overwritten only at the very
end when calling the :py:func:`~FileInsert.close` function or when
exiting a ``with`` clause.
.. sourcecode: python
with FileInsert.open(input_file_path) as fout:
fout.write(some_data)
"""
starttag = "/* <-- SPECIALTAG START --> */"
endtag = "/* <-- SPECIALTAG END --> */"
def __init__(self, inputfile, outputfile=None, encoding="utf-8"):
self.inputfile = inputfile
# If output is the same as input, prepare a temporary file
if outputfile is None or self._samefile(inputfile, outputfile):
randsuffix = "".join(random.sample(string.ascii_letters, 8))
self.outputfile = inputfile + "." + randsuffix
self.in_is_out = True
else:
self.outputfile = outputfile
self.in_is_out = False
self.encoding = encoding
self.in_block = False
self._openfiles()
def _openfiles(self):
# Open the two files
self._fin = open(self.inputfile, "r", encoding=self.encoding)
try:
self._fout = open(self.outputfile, "w", encoding=self.encoding)
except IOError as e:
self._fin.close()
raise
def _samefile(self, src, dst):
# Internal function to decide whether two paths point to the same
# file. Taken from shutil.
# Macintosh, Unix.
if hasattr(os.path, 'samefile'):
try:
return os.path.samefile(src, dst)
except OSError:
return False
# All other platforms: check for same pathname.
return (os.path.normcase(os.path.abspath(src)) ==
os.path.normcase(os.path.abspath(dst)))
def _writebegining(self):
# Internal function that copy all the content of input file to output
# file until the start tag
for line in self._fin:
# Keep the tag in the result file, that's why the check is later
self._fout.write(line)
if line.strip() == self.starttag:
break
else:
self._close(True)
raise SyntaxError("Start markup tag not found in input file")
self.in_block = True
def _close(self, error=False):
# Internal function to close the files in the cleanest manner possible
if self._fin is not None:
self._fin.close()
self._fin = None
if self._fout is not None:
self._fout.close()
self._fout = None
if error and self.in_is_out:
os.unlink(self.outputfile)
def __moveouttoin(self):
# Internal function that moves the temporary output file to replace the
# input file. Only versions older than 3.3, the rename function fails
# on Windows if the destination already exists (and it does exist).
if sys.hexversion >= 0x03030000:
os.replace(self.outputfile, self.inputfile)
else:
if not sys.platform.startswith("win32"):
os.rename(self.outputfile, self.inputfile)
else:
# Push the input file to a temporary location
randsuffix = "".join(random.sample(string.ascii_letters, 8))
newinputfile = self.inputfile + "." + randsuffix
os.rename(self.inputfile, newinputfile)
# Try to push the output to the input file
try:
os.rename(self.outputfile, self.inputfile)
except OSError:
# If it failed, try at least to replace the input file where
# it was.
os.rename(newinputfile, self.inputfile)
raise
# Finally remove the old input file
os.unlink(newinputfile)
def close(self):
"""
End insertion of data and close the different file handles.
This is the step where data placed after the end tag is copied to
the output.
"""
if self.in_block:
# Find the end tag, ignoring the content in-between
for line in self._fin:
if line.strip() == self.endtag:
# Keep the tag in the result file
self._fout.write(line)
break
else:
self._close(True)
raise SyntaxError("End markup tag not found in input file")
self.in_block = False
# Copy the content after the end block
for line in self._fin:
self._fout.write(line)
# Close the file handles
self._close()
# If necessary, move the output to the input
if self.in_is_out:
self.__moveouttoin()
@classmethod
def open(cls, inputfile, outputfile=None, encoding="utf-8"):
"""
Creates a new :py:class:`FileInsert` object
"""
return cls(inputfile, outputfile, encoding)
def __enter__(self):
# Enter context manager. Prepare the file.
self._writebegining()
return self
def write(self, data):
"""
Write data to output file between the two tags
"""
# Verify that the first step has been done
if not self.in_block:
self._writebegining()
self._fout.write(data)
def __exit__(self, exc_type, exc_value, traceback):
# Exit context manager. Close the file handles.
self.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment