Dump will create a human readable version of your data-structure.
    It will try to dump almost anything, it has recursion detection and
    will try to display the recursion in a meaningful way.

    :param   data_structure: The structure to convert.

    When you freeze only content counts, same content same hash

    >>> a = hash(freeze(_TestClass(True)))
    >>> b = hash(freeze(_TestClass(True)))
    >>> b == a
    True

    >>> a = freeze(_TestClass(True))
    >>> b = freeze(_TestClass(True))
    >>> b == a
    True

    >>> x = _TestClass(True)
    >>> a = freeze(dump(x))
    >>> b = freeze(dump(x))
    >>> b == a
    True

    When you dump-freeze only content/type counts, same content/type same hash

    - Two object of the same type with same content will be equal

    - Two object of the different type with same content will be different

    >>> a = hash(freeze(dump(_TestClass(True))))
    >>> b = hash(freeze(dump(_TestClass(True))))
    >>> b == a
    True

    >>> a = freeze(dump(_TestClass(True)))
    >>> b = freeze(dump(_TestClass(True)))
    >>> b == a
    True

    >>> a = hash(freeze(dump(_TestClass(True))))
    >>> b = hash(freeze(dump(_TestClass2(True))))
    >>> b != a
    True

    >>> a = freeze(dump(_TestClass(True)))
    >>> b = freeze(dump(_TestClass2(True)))
    >>> b != a
    True

    >>> _py2_to_py3(vformat(dump([1, {'a' : 'b'}])))
    [1,
     ["<class 'dict'>",
      {'a': 'b'}]]

    >>> vformat(recursive_sort(dump(_TestClass(True))))
    ["<class 'freeze.xfreeze._TestClass'>",
     (('a',
       'huhu'),
      ('sub',
       ["<class 'freeze.xfreeze._TestSlots'>",
        (('a',
          'slot'),
         ('b',
          (1,
           (1,
            2,
            3),
           2,
           3)))]))]

    >>> a = _TestSlots()
    >>> b = [a, 1, 2, [a, "banane"]]
    >>> _no_null_x(vformat(dump(b)))
      {'a': 'slot',
       'b': [1,
             2,
             3,
             (1,
              2,
              3)]}],
     1,
     2,
      'banane']]

    >>> a = [1, 2]
    >>> _no_null_x(vformat(dump((a, (a, a)))))
    ([1,
      2],
     ([1,
       2],
      [1,
       2]))

    >>> recursive_sort(dump(freeze(_TestClass(True))))
    (('a', 'huhu'), ((('a', 'slot'), ('b', (1, (1, 2, 3), 2, 3))), 'sub'))

    >>> dump((None, (None, None)))
    (None, (None, None))

    >>> s = _TestClassWithLen()
    >>> a = [s, s]
    >>> _no_null_x(vformat(dump(a)))
      {'a': 'huhu'}],

    >>> s = (1, 2)
    >>> a = [s, s]
    >>> _no_null_x(vformat(dump(a)))
    [(1,
      2),
     (1,
      2)]

def dump(data_structure):
    """Dump will create a human readable version of your data-structure.
    It will try to dump almost anything, it has recursion detection and
    will try to display the recursion in a meaningful way.

    :param   data_structure: The structure to convert.

    When you freeze only content counts, same content same hash

    >>> a = hash(freeze(_TestClass(True)))
    >>> b = hash(freeze(_TestClass(True)))
    >>> b == a
    True

    >>> a = freeze(_TestClass(True))
    >>> b = freeze(_TestClass(True))
    >>> b == a
    True

    >>> x = _TestClass(True)
    >>> a = freeze(dump(x))
    >>> b = freeze(dump(x))
    >>> b == a
    True

    When you dump-freeze only content/type counts, same content/type same hash

    - Two object of the same type with same content will be equal

    - Two object of the different type with same content will be different

    >>> a = hash(freeze(dump(_TestClass(True))))
    >>> b = hash(freeze(dump(_TestClass(True))))
    >>> b == a
    True

    >>> a = freeze(dump(_TestClass(True)))
    >>> b = freeze(dump(_TestClass(True)))
    >>> b == a
    True

    >>> a = hash(freeze(dump(_TestClass(True))))
    >>> b = hash(freeze(dump(_TestClass2(True))))
    >>> b != a
    True

    >>> a = freeze(dump(_TestClass(True)))
    >>> b = freeze(dump(_TestClass2(True)))
    >>> b != a
    True

    >>> _py2_to_py3(vformat(dump([1, {'a' : 'b'}])))
    [1,
     ["<class 'dict'>",
      {'a': 'b'}]]

    >>> vformat(recursive_sort(dump(_TestClass(True))))
    ["<class 'freeze.xfreeze._TestClass'>",
     (('a',
       'huhu'),
      ('sub',
       ["<class 'freeze.xfreeze._TestSlots'>",
        (('a',
          'slot'),
         ('b',
          (1,
           (1,
            2,
            3),
           2,
           3)))]))]

    >>> a = _TestSlots()
    >>> b = [a, 1, 2, [a, "banane"]]
    >>> _no_null_x(vformat(dump(b)))
      {'a': 'slot',
       'b': [1,
             2,
             3,
             (1,
              2,
              3)]}],
     1,
     2,
      'banane']]

    >>> a = [1, 2]
    >>> _no_null_x(vformat(dump((a, (a, a)))))
    ([1,
      2],
     ([1,
       2],
      [1,
       2]))

    >>> recursive_sort(dump(freeze(_TestClass(True))))
    (('a', 'huhu'), ((('a', 'slot'), ('b', (1, (1, 2, 3), 2, 3))), 'sub'))

    >>> dump((None, (None, None)))
    (None, (None, None))

    >>> s = _TestClassWithLen()
    >>> a = [s, s]
    >>> _no_null_x(vformat(dump(a)))
      {'a': 'huhu'}],

    >>> s = (1, 2)
    >>> a = [s, s]
    >>> _no_null_x(vformat(dump(a)))
    [(1,
      2),
     (1,
      2)]
    """

    identity_set = set()
    dup_set      = set()

    def dump_helper(data_structure):
        if data_structure is None:
            return None
        # Primitive types don't need processing
        if isinstance(data_structure, _primitive_types):
            return data_structure
        # Cycle detection
        idd = id(data_structure)
        if idd in identity_set:
            # We do not recurse into containers
            tlen = -1
            try:
                tlen = len(data_structure)
            except:  # pragma: no cover
                pass
            if tlen != -1:
                # We do not recurse into dictizable objects
                if (
                    hasattr(data_structure, "__dict__") or
                    hasattr(data_structure, "__slots__")
                ):
                    # Special case where __len__ is implemented
                    dup_set.add(idd)
                    return "R: %s at 0x%X" % (type(data_structure), idd)
                # Except string and tuples
                if not isinstance(
                        data_structure,
                        _ignore_types
                ):  # pragma: no cover
                    dup_set.add(idd)
                    return "R: %s at 0x%X" % (type(data_structure), idd)
            else:
                dup_set.add(idd)
                return "R: %s at 0x%X" % (type(data_structure), idd)

        else:
            identity_set.add(idd)
        ret = Meta()
        ret.append(IDD(data_structure, idd))
        was_dict = isinstance(data_structure, WasDict)
        was_tuple = isinstance(data_structure, tuple)
        if not was_dict:
            if hasattr(data_structure, "__slots__"):
                data_structure = dict(object_to_items(data_structure))
                was_dict = True
            else:
                # Dictize if possible (support objects)
                try:
                    data_structure = data_structure.__dict__
                    was_dict = True
                except:
                    pass
        # Itemize if possible
        try:
            data_structure = data_structure.items()
            ret.append(dict([
                (dump_helper(x[0]), dump_helper(x[1]))
                for x in data_structure
            ]))
            return ret
        except:
            pass
        tlen = -1
        # If item has a length we dump it
        try:
            tlen = len(data_structure)
        except:  # pragma: no cover
            pass
        if tlen != -1:
            # Well there are classes out in the wild that answer to len
            # but have no indexer.
            try:
                if was_dict:
                    ret.append(WasDict([
                        (dump_helper(x[0]), dump_helper(x[1]))
                        for x in data_structure
                    ]))
                elif was_tuple:
                    ret.append(tuple([
                        dump_helper(x) for x in data_structure
                    ]))
                else:
                    ret.append([
                        dump_helper(x) for x in data_structure
                    ])
                return ret
            except:  # pragma: no cover
                pass
        ret.append(data_structure)  # pragma: no cover
        return ret  # pragma: no cover

    def clean_up(data_structure):
        if isinstance(data_structure, Meta):
            idd_temp = data_structure[0]
            idd_temp.is_target = idd_temp.idd in dup_set
            if not (
                idd_temp.is_target or
                idd_temp.is_visible
            ):
                del data_structure[0]
            if len(data_structure) == 1:
                data_structure = data_structure[0]
        # We don't clean strings
        if not isinstance(data_structure, _string_types):
            tlen = -1
            # If item has a length we clean it
            try:
                tlen = len(data_structure)
            except:
                pass
            if tlen != -1:
                if isinstance(data_structure, dict):
                    for k in data_structure.keys():
                        data_structure[k] = clean_up(data_structure[k])
                elif isinstance(data_structure, Meta):
                    data_structure = Meta([
                        clean_up(x) for x in data_structure
                    ])
                elif isinstance(data_structure, tuple):
                    data_structure = tuple([
                        clean_up(x) for x in data_structure
                    ])
                else:
                    data_structure = [
                        clean_up(x) for x in data_structure
                    ]
        return data_structure
    data = clean_up(dump_helper(data_structure))
    return data