Split UK postcode into outcode and incode portions.

    Arguments:
    postcode            The postcode to be split.
    strict              If true, the postcode will be validated according to
                        the rules as specified at the Universal Postal Union[1]
                        and The UK Government Data Standards Catalogue[2]. If
                        the supplied postcode doesn't adhere to these rules a
                        ValueError will be thrown.
    incode_mandatory    If true, and only an outcode has been supplied, the
                        function will throw a ValueError.

    Returns:            outcode, incode

    Raises:             ValueError, if postcode is longer than seven
                        characters, or if 'strict' or 'incode_mandatory'
                        conditions are broken - see above.

    Usage example:      >>> from postcode import parse_uk_postcode
                        >>> parse_uk_postcode('cr0 2yr')
                        ('CR0', '2YR')
                        >>> parse_uk_postcode('cr0')
                        Traceback (most recent call last):
                          File "<interactive input>", line 1, in ?
                          File "postcode.py", line 101, in parse_uk_postcode
                            raise ValueError('Incode mandatory')
                        ValueError: Incode mandatory
                        >>> parse_uk_postcode('cr0', False, False)
                        ('CR0', '')

    [1] http://www.upu.int/fileadmin/documentsFiles/activities/addressingUnit/gbrEn.pdf
    [2] http://web.archive.org/web/20090930140939/http://www.govtalk.gov.uk/gdsc/html/noframes/PostCode-2-1-Release.htm

def parse_uk_postcode(postcode, strict=True, incode_mandatory=True):
    '''Split UK postcode into outcode and incode portions.

    Arguments:
    postcode            The postcode to be split.
    strict              If true, the postcode will be validated according to
                        the rules as specified at the Universal Postal Union[1]
                        and The UK Government Data Standards Catalogue[2]. If
                        the supplied postcode doesn't adhere to these rules a
                        ValueError will be thrown.
    incode_mandatory    If true, and only an outcode has been supplied, the
                        function will throw a ValueError.

    Returns:            outcode, incode

    Raises:             ValueError, if postcode is longer than seven
                        characters, or if 'strict' or 'incode_mandatory'
                        conditions are broken - see above.

    Usage example:      >>> from postcode import parse_uk_postcode
                        >>> parse_uk_postcode('cr0 2yr')
                        ('CR0', '2YR')
                        >>> parse_uk_postcode('cr0')
                        Traceback (most recent call last):
                          File "<interactive input>", line 1, in ?
                          File "postcode.py", line 101, in parse_uk_postcode
                            raise ValueError('Incode mandatory')
                        ValueError: Incode mandatory
                        >>> parse_uk_postcode('cr0', False, False)
                        ('CR0', '')

    [1] http://www.upu.int/fileadmin/documentsFiles/activities/addressingUnit/gbrEn.pdf
    [2] http://web.archive.org/web/20090930140939/http://www.govtalk.gov.uk/gdsc/html/noframes/PostCode-2-1-Release.htm
    '''

    postcode = postcode.replace(' ', '').upper()  # Normalize

    if len(postcode) > 7:
        raise exceptions.MaxLengthExceededError()

    # Validate postcode
    if strict:

        # Try for full postcode match
        postcode_match = POSTCODE_REGEX.match(postcode)
        if postcode_match:
            return postcode_match.group(1, 2)

        # Try for outcode only match
        outcode_match = STANDALONE_OUTCODE_REGEX.match(postcode)
        if outcode_match:
            if incode_mandatory:
                raise exceptions.IncodeNotFoundError('Incode mandatory')
            else:
                return outcode_match.group(1), ''

        # Try Girobank special case
        if postcode == 'GIR0AA':
            return 'GIR', '0AA'
        elif postcode == 'GIR':
            if incode_mandatory:
                raise exceptions.IncodeNotFoundError('Incode mandatory')
            else:
                return 'GIR', ''

        # None of the above
        raise exceptions.InvalidPostcodeError(
            'Value provided does not align with UK postcode rules'
        )

    # Just chop up whatever we've been given.
    else:
        # Outcode only
        if len(postcode) <= 4:
            if incode_mandatory:
                raise exceptions.IncodeNotFoundError('Incode mandatory')
            else:
                return postcode, ''
        # Full postcode
        else:
            return postcode[:-3], postcode[-3:]