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