##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test the Dublib Core Structured Value support functions.

$Id: test_dcsv.py 37557 2005-07-29 18:19:31Z benji_york $
"""
import unittest

from zope.testing.doctestunit import DocTestSuite
from zope.app.dublincore.dcsv import encode, decode


# TODO still need tests for errors, and createMapping()


def test_decode_empty():
    """
    >>> decode('')
    []
    >>> decode('   ')
    []
    """

def test_decode_simple_value():
    """
    >>> decode('v')
    [('', 'v')]
    >>> decode(' v ')
    [('', 'v')]
    >>> decode('v;')
    [('', 'v')]
    >>> decode(' v ; ')
    [('', 'v')]
    """

def test_decode_simple_escaped_value():
    # Make the docstring a raw string to avoid having escapes
    # interpreted twice; each test within the docstring will be parsed
    # again!
    r"""
    >>> decode(r'\v')
    [('', 'v')]
    >>> decode(r'\;')
    [('', ';')]
    >>> decode(r'\;;')
    [('', ';')]
    >>> decode(r'\= ')
    [('', '=')]
    >>> decode(r'\= ; ')
    [('', '=')]

    >>> decode(r'\\\=\; ; ')
    [('', '\\=;')]
    >>> decode(r'\\\=\;')
    [('', '\\=;')]
    >>> decode(r'\\\=\; = ; ')
    [('\\=;', '')]
    >>> decode(r'\;\;\;;')
    [('', ';;;')]
    """

def test_decode_trailing_backslash():
    r"""
    >>> decode('\\')
    [('', '\\')]
    >>> decode('v\\')
    [('', 'v\\')]

    These are tricky, but for different reasons:

    >>> decode(r'v\ ')
    [('', 'v\\')]
    >>> decode(r'v\ ; ')
    [('', 'v')]
    """

def test_decode_simple_list():
    """
    >>> decode('a;b;c')
    [('', 'a'), ('', 'b'), ('', 'c')]
    >>> decode('a;b;c;')
    [('', 'a'), ('', 'b'), ('', 'c')]
    """

def test_decode_simple_escaped_list():
    r"""
    >>> decode(r'\a;\b;\c')
    [('', 'a'), ('', 'b'), ('', 'c')]
    >>> decode(r' \a ; \b ; \c ; ')
    [('', 'a'), ('', 'b'), ('', 'c')]

    >>> decode(r'\;;b;c')
    [('', ';'), ('', 'b'), ('', 'c')]
    >>> decode(r' \=;b;c;')
    [('', '='), ('', 'b'), ('', 'c')]
    """

def test_decode_empty_values():
    # weird case; hard to know the intent of the specification
    """
    >>> decode('=')
    [('', '')]
    >>> decode(';')
    [('', '')]
    >>> decode('  ;  ')
    [('', '')]
    >>> decode(';;')
    [('', ''), ('', '')]
    >>> decode('  ;  ;  ')
    [('', ''), ('', '')]
    >>> decode('=;')
    [('', '')]
    >>> decode(' = ;  ')
    [('', '')]
    >>> decode('=;=;')
    [('', ''), ('', '')]
    >>> decode(' = ; = ;  ')
    [('', ''), ('', '')]
    >>> decode(' = ; = ; = ')
    [('', ''), ('', ''), ('', '')]
    """

def test_decode_labeled_values():
    """
    >>> decode('a=b')
    [('a', 'b')]
    >>> decode('a=b;')
    [('a', 'b')]
    >>> decode('a=b;c=d')
    [('a', 'b'), ('c', 'd')]

    Not really sure about this one yet; assuming that the space in 'd ;'
    is supposed to be removed until we have information that says
    otherwise:

    >>> decode('a =b; c=  d ;')
    [('a', 'b'), ('c', 'd')]
    """

def test_decode_mixed_values():
    """
    >>> decode('a;b=c')
    [('', 'a'), ('b', 'c')]
    >>> decode('a=b;c')
    [('a', 'b'), ('', 'c')]
    >>> decode('a;b=c;  ')
    [('', 'a'), ('b', 'c')]
    >>> decode('a=b;c ; ')
    [('a', 'b'), ('', 'c')]

    >>> decode('a;b;c;d=e;f;g;')
    [('', 'a'), ('', 'b'), ('', 'c'), ('d', 'e'), ('', 'f'), ('', 'g')]
    >>> decode('a=b;c=d;e;f=g')
    [('a', 'b'), ('c', 'd'), ('', 'e'), ('f', 'g')]
    """

def test_decode_duplicate_labels():
    """
    >>> decode('a=b;a=c; a=d')
    [('a', 'b'), ('a', 'c'), ('a', 'd')]
    """

def test_encode_empty_list():
    """
    >>> encode([])
    ''
    """

def test_encode_single_item():
    """
    >>> encode([''])
    ';'
    >>> encode([('', '')])
    ';'
    >>> encode(['a'])
    'a;'
    >>> encode([('', 'a')])
    'a;'
    >>> encode([('a','')])
    'a=;'
    >>> encode([('a', 'b')])
    'a=b;'

    The label from a pair can be any non-true value:

    >>> encode([(None, '')])
    ';'
    >>> encode([(None, 'a')])
    'a;'
    >>> encode([(0, 'a')])
    'a;'
    >>> encode([((), 'a')])
    'a;'

    This may be a mis-feature, but seems harmless since no one in
    their right mind would use it intentionally (except maybe with
    None).
    """

def test_encode_single_value_needing_escapes():
    r"""
    >>> encode(['='])
    '\\=;'
    >>> encode([';'])
    '\\;;'
    >>> encode(['\\'])
    '\\\\;'
    >>> encode([r'\\'])
    '\\\\\\\\;'
    """

def test_encode_labeled_value_needing_escapes():
    r"""
    Escaping needed in the labels:

    >>> encode([('\\', '')])
    '\\\\=;'
    >>> encode([('\\', 'a')])
    '\\\\=a;'
    >>> encode([('=', '')])
    '\\==;'
    >>> encode([(';', 'a')])
    '\\;=a;'

    Escaping needed in the values:

    >>> encode([('a', '\\')])
    'a=\\\\;'
    >>> encode([('a', '=')])
    'a=\\=;'
    >>> encode([('a', ';')])
    'a=\\;;'

    Escaping needed in both:

    >>> encode([('\\', '\\')])
    '\\\\=\\\\;'
    >>> encode([('=', '=')])
    '\\==\\=;'
    >>> encode([(';', ';')])
    '\\;=\\;;'
    """

def test_encode_simple_list():
    """
    >>> encode(['a', 'b', 'c'])
    'a; b; c;'
    >>> encode(['', '', ''])
    '; ; ;'
    >>> encode(['a b', 'c d'])
    'a b; c d;'
    """

def test_encode_labeled_values():
    # Single items were tested above; these all demonstrate with more
    # than one item.
    """
    >>> encode([('a', ''), ('b', '')])
    'a=; b=;'
    >>> encode([('a', 'b'), ('c', 'd')])
    'a=b; c=d;'
    """

def test_encode_mixed_items():
    """
    >>> encode(['a', ('b', 'c')])
    'a; b=c;'
    >>> encode([('', 'a'), ('b', 'c')])
    'a; b=c;'
    >>> encode([('b', 'c'), 'a'])
    'b=c; a;'
    >>> encode([('b', 'c'), ('', 'a')])
    'b=c; a;'
    """

def test_encode_error_non_strings():
    """
    >>> encode([(42, '')])
    Traceback (most recent call last):
    ...
    TypeError: labels must be strings; found 42
    >>> encode([('', 42)])
    Traceback (most recent call last):
    ...
    TypeError: values must be strings; found 42
    >>> encode([('label', 42)])
    Traceback (most recent call last):
    ...
    TypeError: values must be strings; found 42
    """

def test_encode_error_outer_whitespace():
    """
    >>> encode([' a'])
    Traceback (most recent call last):
    ...
    ValueError: values may not include leading or trailing spaces: ' a'
    >>> encode(['a '])
    Traceback (most recent call last):
    ...
    ValueError: values may not include leading or trailing spaces: 'a '
    >>> encode([('', 'a ')])
    Traceback (most recent call last):
    ...
    ValueError: values may not include leading or trailing spaces: 'a '
    >>> encode([('label', 'a ')])
    Traceback (most recent call last):
    ...
    ValueError: values may not include leading or trailing spaces: 'a '
    """


def test_suite():
    return DocTestSuite()

if __name__ == '__main__':
    unittest.main(defaultTest="test_suite")
