

class SourceLists(object):
    """Represents a list of sources lists for apt
    """

    def __init__(self, manifest_vars):
        """
        :param dict manifest_vars: The manifest variables
        """
        # A dictionary with the name of the file in sources.list.d as the key
        # That values are lists of Source objects
        self.sources = {}
        # Save the manifest variables, we need the later on
        self.manifest_vars = manifest_vars

    def add(self, name, line):
        """Adds a source to the apt sources list

        :param str name: Name of the file in sources.list.d, may contain manifest vars references
        :param str line: The line for the source file, may contain manifest vars references
        """
        name = name.format(**self.manifest_vars)
        line = line.format(**self.manifest_vars)
        if name not in self.sources:
            self.sources[name] = []
        self.sources[name].append(Source(line))

    def target_exists(self, target):
        """Checks whether the target exists in the sources list

        :param str target: Name of the target to check for, may contain manifest vars references

        :return: Whether the target exists
        :rtype: bool
        """
        target = target.format(**self.manifest_vars)
        # Run through all the sources and return True if the target exists
        for lines in self.sources.itervalues():
            if target in (source.distribution for source in lines):
                return True
        return False


class Source(object):
    """Represents a single source line
    """

    def __init__(self, line):
        """
        :param str line: A apt source line

        :raises SourceError: When the source line cannot be parsed
        """
        # Parse the source line and populate the class attributes with it
        # The format is taken from `man sources.list`
        # or: http://manpages.debian.org/cgi-bin/man.cgi?sektion=5&query=sources.list&apropos=0&manpath=sid&locale=en
        import re
        regexp = re.compile('^(?P<type>deb|deb-src)\s+'
                            '(\[\s*(?P<options>.+\S)?\s*\]\s+)?'
                            '(?P<uri>\S+)\s+'
                            '(?P<distribution>\S+)'
                            '(\s+(?P<components>.+\S))?\s*$')
        match = regexp.match(line).groupdict()
        if match is None:
            from exceptions import SourceError
            raise SourceError('Unable to parse source line: ' + line)
        self.type = match['type']
        self.options = []
        if match['options'] is not None:
            self.options = re.sub(' +', ' ', match['options']).split(' ')
        self.uri = match['uri']
        self.distribution = match['distribution']
        self.components = []
        if match['components'] is not None:
            self.components = re.sub(' +', ' ', match['components']).split(' ')

    def __str__(self):
        """Convert the object into a source line
        This is pretty much the reverse of what we're doing in the initialization function.

        :rtype: str
        """
        options = ''
        if len(self.options) > 0:
            options = ' [{options}]'.format(options=' '.join(self.options))

        components = ''
        if len(self.components) > 0:
            components = ' {components}'.format(components=' '.join(self.components))

        return ('{type}{options} {uri} {distribution}{components}'
                .format(type=self.type, options=options,
                        uri=self.uri, distribution=self.distribution,
                        components=components))
