For completeness, here is a copy of the macro:

------------------------------------------------------------------------------------------

import sys, re, StringIO, cgi
from MoinMoin.Page import Page


class SortByError(Exception):
    """Raised anywhere in the macro, caught outside the main loop."""

    def __init__(self, msg=''):
        """msg -- a string to be displayed to the user"""
        self.msg = msg


def error(msg, args):
    """Return a message followed by usage information, all in HTML.

    msg  -- a string describing the error
    args -- the macro's original argument string

    """
    html = """
<tt class="wiki">[[SortBy(%s)]]<
/tt><br>
<strong class="error">SortBy: %s</strong><br>
<small>%s<br>
Valid sort types are: %s</small>"""
    msg = cgi.escape(msg)
    usage = cgi.escape("""Usage: SortBy(TablePage, <number of header rows>,
            <primary sort column number>, <sort type>
            [, <secondary sort column>, <sort type>] ... )""")
    sorts = ' '.join(valid_sorts)
    return html % (args, msg, usage, sorts)


def read_table(page_file):
    """Read page_file and convert its first table's lines into row data"""
    file = open(page_file)
    intable = 0
    table_rows = []
    for line in file.xreadlines():
        if len(line) < 2 or line[0:2] != '||':
            if intable: break # We're done
            else: continue    # Skip non-table lines until we hit the table
        else:
            intable = 1
            table_rows.append(line[:-1]) # strip the \n while we're at it
    return table_rows


def strip_fmt(arg):
    """Remove formatting characters and leading/trailing whitespace.

    Commas (such as in 1,000,000) are considered formatting chars."""
    for fmt_string in ["'''", "''", '{{{', '}}}', ',']:
        arg = arg.replace(fmt_string, '')
    return arg.strip()


def to_number(arg):
    """Convert arg to int or float if possible, else return a string."""
    arg = strip_fmt(arg)
    try: return int(arg)
    except ValueError:
        try: return float(arg)
        except ValueError: return arg


def to_nocase(arg):
    """Return arg in lowercase with no formatting characters."""
    return strip_fmt(arg).lower()


def to_nosort(arg, count=[0]):
    """Return a higher integer each time so rows don't move when sorted."""
    count[0] += 1   # count is a default arg, so references the same list and
    return count[0] # incr's the same [0] every time the function is called.


decorate_functions = {'alpha': strip_fmt,
                      'number': to_number,
                      'nocase': to_nocase,
                      'nosort': to_nosort}
valid_sorts = decorate_functions.keys()
valid_sorts.sort()
valid_sorts.extend(['reverse'+sort_name for sort_name in valid_sorts])


def process_args(args, request):
    """Parse args string, return (sort_page, table, num_headers, sort_list)."""
    arglist = re.split(r'\s*,\s*', args.strip())
    if len(arglist) < 4:
        msg = """Not enough arguments (%s).
                 At least 4 are required.""" % len(arglist)
        raise SortByError, msg
    table_page, num_headers = arglist[0:2]
    try: num_headers = int(num_headers)
    except ValueError:
        msg = """Number of header rows (%s)
                 must be an integer""" % num_headers
        raise SortByError, msg
    if num_headers < 0:
        msg = """Number of header rows (%s)
                 must be a positive integer""" % num_headers
        raise SortByError, msg
    arglist[:2] = []  # Make arglist contain only column & sort_type pairs
    if len(arglist)%2 != 0:
        raise SortByError, 'Odd number of arguments (%s)' % (len(arglist)+2)
    sort_list = []
    while arglist:

        # Pop the sort_type and column from the end of the arglist.
        # They get stored in the sort_list in the opposite order
        # they were requested so the primary sort is done last.

        sort_type = arglist.pop().lower()
        sort_column = arglist.pop()
        try: sort_column = int(sort_column)
        except ValueError:
            msg = 'Column number (%s) must be an integer' % sort_column
            raise SortByError, msg
        if sort_column < 1:
            msg = """Column number (%s) must be 1 or higher.
                     1 = leftmost column""" % sort_column
            raise SortByError, msg
        sort_column -= 1  # Use zero-based column indexing internally
        if sort_type[:7] == 'reverse': # If the sort_type begins with 'reverse'
            reverse = 1                # set the reverse flag
            sort_type = sort_type[7:]  # and strip 'reverse' from sort_type
        else: reverse = 0
        sort_list.append((sort_column, sort_type, reverse))  # Append a 3-tuple
    sort_page = Page(request, table_page)
    try: table_rows = read_table(sort_page._text_filename())
    except IOError:
        raise SortByError, 'Unable to open the table page "%s"' % table_page
    table = [row.split('||')[1:-1] for row in table_rows]
    if num_headers > len(table):
        msg = """Number of header rows (%s) is more than
                 the table length (%s)""" % (num_headers, len(table))
        raise SortByError, msg
    return (sort_page, table, num_headers, sort_list)


def sort_table(table, num_headers, sort_column, sort_type, reverse):
    """Sort of the table (in-place), preserving num_headers rows at the top.

    Arguments:
    table       -- a list of lists representing rows of column entries
    num_headers -- an integer number of rows to keep at the top of the table
    sort_column -- the column number (zero-based) containing the sort values
    sort_type   -- which kind of sort to perform on the values being sorted
    reverse     -- 0 or 1, meaning an ascending or descending (reverse) sort

    """
    header = table[:num_headers]
    table[:num_headers] = []

    if table and sort_column > min([len(row) for row in table]):
        msg = """Column number (%s) is higher than
                 the length of one or more rows"""  % (sort_column+1)
        raise SortByError, msg
    if sort_type not in valid_sorts:
        raise SortByError, 'Invalid sort type "%s"' % sort_type
    decorate = decorate_functions[sort_type]

    # Use the 'decorate, sort, undecorate' pattern with ascending or
    # descending indices to ensure that the sort is stable.

    decorations = [decorate(row[sort_column]) for row in table]
    if reverse: indices = xrange(len(table), 0, -1)
    else: indices = xrange(len(table))
    decorated = zip(decorations, indices, table)
    decorated.sort()
    table[:] = [row for (decoration, index, row) in decorated]

    if reverse: table.reverse()
    table[:0] = header
    return


def format(sort_page, macro, table):
    """Format the sorted table and return it as an HTML fragment.

    Arguments:
    sort_page -- a MoinMoin Page object representing the table page
    macro     -- the macro argument that was provided by MoinMoin
    table     -- a list of lists representing rows of column entries

    """
    ret = ''
    table_rows = ['||%s||' % '||'.join(row) for row in table]
    sort_page.set_raw_body('\n'.join(table_rows), 1)  # format the table

    # Here's some tricky stuff copied from Richard Jones' Include macro.
    stdout = sys.stdout
    sys.stdout = StringIO.StringIO()
    sort_page.send_page(macro.request, content_only=1)
    ret += sys.stdout.getvalue()
    sys.stdout = stdout

    # Output a helper link to get to the page with the table.
    name = sort_page.page_name
    ret += '<small>' + macro.formatter.url(1, name) + \
           macro.formatter.text('[goto %s]' % name) + macro.formatter.url(0) + \
           '</small>'
    return ret


def execute(macro, args):
    """Parse args, sort_table() repeatedly, return formatted table as HTML.

    Sort the table once for each column & sort_type in the sort_list
    beginning with the last (and least significant) sort in args
    because they were 'popped' from the end of the args. Since
    sort_table() uses a stable sort algorithm, the ordering of the
    earlier sorts is preserved in the later sorts when there are
    duplicates of the sort key.

    """
    try:
        sort_page, table, num_headers, sort_list = process_args(args,
macro.request)
        for sort_column, sort_type, reverse in sort_list:
            sort_table(table, num_headers, sort_column, sort_type, reverse)
        return format(sort_page, macro, table)
    except SortByError, e: return error(e.msg, args)

-------------------------------------------------------------------------
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://sourceforge.net/services/buy/index.php
_______________________________________________
Moin-user mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/moin-user

Reply via email to