<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def show_keys(cls, keys=None, indent=0, grouped=False, func=None, include_links=False, *args, **kwargs):
 """ Classmethod to return a nice looking table with the given formatoptions Parameters %(Plotter._enhance_keys.parameters)s indent: int The indentation of the table grouped: bool, optional If True, the formatoptions are grouped corresponding to the :attr:`Formatoption.groupname` attribute Other Parameters func: function or None The function the is used for returning (by default it is printed via the :func:`print` function or (when using the gui) in the help explorer). The given function must take a string as argument include_links: bool or None, optional Default False. If True, links (in restructured formats) are included in the description. If None, the behaviour is determined by the :attr:`psyplot.plotter.Plotter.include_links` attribute. %(Plotter._enhance_keys.other_parameters)s Returns ------- results of `func` None if `func` is the print function, otherwise anything else See Also -------- show_summaries, show_docs"""

        def titled_group(groupname):
            bars = str_indent + '*' * len(groupname) + '\n'
            return bars + str_indent + groupname + '\n' + bars

        keys = cls._enhance_keys(keys, *args, **kwargs)
        str_indent = " " * indent
        func = func or default_print_func
        # call this function recursively when grouped is True
        if grouped:
            grouped_keys = DefaultOrderedDict(list)
            for fmto in map(lambda key: getattr(cls, key), keys):
                grouped_keys[fmto.groupname].append(fmto.key)
            text = ""
            for group, keys in six.iteritems(grouped_keys):
                text += titled_group(group) + cls.show_keys(
                    keys, indent=indent, grouped=False, func=six.text_type,
                    include_links=include_links) + '\n\n'
            return func(text.rstrip())

        if not keys:
            return
        n = len(keys)
        ncols = min([4, n])  # number of columns
        # The number of cells in the table is one of the following cases:
        #     1. The number of columns and equal to the number of keys
        #     2. The number of keys
        #     3. The number of keys plus the empty cells in the last column
        ncells = n + ((ncols - (n % ncols)) if n != ncols else 0)
        if include_links or (include_links is None and cls.include_links):
            long_keys = list(map(lambda key: ':attr:`~%s.%s.%s`' % (
                cls.__module__, cls.__name__, key), keys))
        else:
            long_keys = keys
        maxn = max(map(len, long_keys))  # maximal lenght of the keys
        # extend with empty cells
        long_keys.extend([' ' * maxn] * (ncells - n))
        bars = (str_indent + '+-' + ("-"*(maxn) + "-+-")*ncols)[:-1]
        lines = ('| %s |\n%s' % (' | '.join(
            key.ljust(maxn) for key in long_keys[i:i+ncols]), bars)
            for i in range(0, n, ncols))
        text = bars + "\n" + str_indent + ("\n" + str_indent).join(
            lines)
        if six.PY2:
            text = text.encode('utf-8')

        return func(text)