Blob Blame History Raw
#!/usr/bin/python3
# -*- coding: utf-8 -*-

# Copyright (C) 2018 SUSE LLC
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
# USA.

import io
import re
import sys

import exc


class Patch(object):
    # This pattern was copied from quilt/scripts/patchfns.in:patch_header() in
    # the quilt sources
    break_matcher = re.compile(b"(---|\*\*\*|Index:)[ \t][^ \t]|^diff -")

    def __init__(self, f):
        assert(f.tell() == 0)
        assert(isinstance(f, io.BufferedIOBase)) # binary (bytes) io object

        self.modified = False
        self.f = f
        self.head = []
        self.body = b""
        for line in f:
            if self.break_matcher.match(line):
                self.body = line
                break
            self.head.append(line.decode())


    def __del__(self):
        self.writeback()


    def __enter__(self):
        return self


    def __exit__(self, *args):
        self.writeback()


    def writeback(self):
        if not self.modified:
            return

        self.body = self.body + self.f.read()
        self.f.seek(0)
        self.f.writelines([line.encode() for line in self.head])
        self.f.write(self.body)
        self.f.truncate()

        self.modified = False


    def get(self, tag):
        """
        tag does not contain the terminal ": ". It is case insensitive.

        Returns a list with the value for each instance of the tag.
        """
        start = "%s: " % (tag.lower(),)
        return [line[len(start):].strip()
                for line in self.head
                if line.lower().startswith(start)]


    def remove(self, tag):
        """
        Removes all instances of the tag.

        tag does not contain the terminal ": ". It is case insensitive.
        """
        if not self.f.writable():
            raise exc.KSException("Modification of read-only Patch")

        if len(self.get(tag)):
            self.modified = True
            start = "%s: " % (tag.lower(),)
            self.head = [line
                         for line in self.head
                         if not line.lower().startswith(start)]


    def change(self, tag, value):
        """
        Changes the value of all instances of the tag.

        tag does not contain the terminal ": ". It is case insensitive.
        """
        if not self.f.writable():
            raise exc.KSException("Modification of read-only Patch")

        if len(self.get(tag)):
            self.modified = True
            start = "%s: " % (tag.lower(),)

            def change_value(line):
                if line.lower().startswith(start):
                    return "%s%s\n" % (line[:len(start)], value.strip(),)
                else:
                    return line

            self.head = [change_value(line) for line in self.head]
        else:
            raise KeyError("Tag \"%s\" not found" % (tag,))