""" MoinMoin - IndentTable.py processor Processor for turning indented lists of data into tables. With classical MoinMoin tables syntax: * the source can become pretty unreadable when cells contents grow * it is difficult to handle multiple lines * it is impossible to have lists (other than by including a page in a cell) This processor is meant to correct the above problems, by the means of an indented list: * each cell consists of one line * its indent determines its position in the table * one line can be continued on the next line by means of '\' at the end; this helps keeping long contents readable * macros accepted in regular tables are accepted too, e.g [[BR]] * all regular table formatting (formats in <>'s, cell spanning) are supported * optionally, the cell contents can be parsed, allowing to have any kind of lists, headings, etc. The price to pay for the increased power and readability of the source, versus the regular table syntax, is the loss of the 2D matrix layout. @copyright: Pascal Bauermeister @license: GPL Updates: * [v0.0.1] Pascal - Thu Jul 29 15:41:25 CEST 2004 - initial release ------------------------------------------------------------------------------- Usage: {{{#!IndentTable OPTIONS indented data }}} Options: debug insert debug info in the output debug=full insert more debug info in the output +, extended parse each cell content (useful for lists and headings) - force row mode | force column mode ------------------------------------------------------------------------------- Samples: {{{#!IndentTable 1 1 2 ==> 2 3 3 4 4 }}} {{{#!IndentTable 1 2 ==> 1 2 3 4 3 4 }}} {{{#!IndentTable 1 2 ==> 1 2 3 3 4 4 }}} {{{#!IndentTable - 1 2 ==> 1 2 3 3 4 4 }}} {{{#!IndentTable 1 2 ==> 1 2 3 3 4 4 }}} {{{#!IndentTable 1 2 ==> 1 2 3 3 4 4 }}} {{{#!IndentTable 1 2 ==> 1 3 4 3 2 4 }}} {{{#!IndentTable - 1 2 ==> 1 3 2 3 4 4 }}} {{{#!IndentTable 1 2 ==> 1 3 5 3 2 4 6 4 5 6 }}} {{{ #!IndentTable + 1 2 3 4 ==> 1 2 3 <|2(>== Cool ! ==\ [[SystemInfo]]\ 4 == Cool! == 6 Terrific, isn't it ? 6 7 9 7 Terrific, isn't it ? 9 10 11 12 10 11 12 }} {{{#!IndentTable + A1 ==> +----+-----------------+------------+--+ B1 |A1 |B1 | C1+D1 | | ||C1+D1 +----+-----------------+------------+--+ A2 | | |C2: Bullets:| | B2 | | | * bullet 1|D2| C2: Bullets: \ |A2 |B2 | * bullet 2| | * bullet 1 \ | | |end of cell | | * bullet 2 \ +----+-----------------+------------+--+ end of cell |A3 | a. (B3) numberes|C3 | | D2 | | b. numbered item| | | A3 +----+-----------------+------------+--+ a. (B3) numbers \ |(A4)|B4 | | | a. numbered item +----+-----------------+------------+--+ C3 '''''' (A4) B4 ## You find this list unreadable ? try to do the same with just ||'s ! }}} """ from MoinMoin.parser import wiki import cStringIO, re, string, random LETTERS = string.ascii_lowercase LETTERS_LEN = len (LETTERS) LIST_HEADERS = ["*", "1.", "a.", "A.", "i.", "I."] COL_FMT_RE1 = re.compile("\\|*<[^>]*>") COL_FMT_RE2 = re.compile("\\|*") # FIXME: unify these two regexes def process (request, formatter, lines): # default options values opt_dbg = False opt_dbgf = False # verbose debug opt_ext = False opt_row = False opt_col = False # not sure this is really useful... substitute_content = True # parse bangpath for arguments bang = lines [0] for arg in bang.split () [1:]: if arg=="debug": opt_dbg = True if arg=="debug=full": opt_dbgf = True elif arg=="extended": opt_ext = True elif arg=="+": opt_ext = True elif arg=="-": opt_row = True elif arg=="|": opt_col = True # remove bang path del lines [0] # # collect src lines # lines_info = [] line_buf = "" last_indent = -1 nb_indent_eq, nb_indent_dec = 0, 0 for line in lines: # skip comments if line.lstrip ().startswith ("##"): continue # handle unterminated lines if line.strip ().endswith ("\\"): line_buf = line_buf + line.rstrip ('\\ ') if opt_ext: line_buf = line_buf + "\n" continue # continue, to finish line # append current line to any previously unterminated line else: line_buf = line_buf + line # skip empty lines if len (line_buf.strip ()) == 0: continue # calculate indent lline = line_buf.lstrip () cur_indent = len (line_buf) - len (lline) if cur_indent == last_indent: nb_indent_eq = nb_indent_eq + 1 if cur_indent < last_indent: nb_indent_dec = nb_indent_dec + 1 # detect table formatting m = COL_FMT_RE1.match (lline) or COL_FMT_RE2.match (lline) if m and m.start() == 0: fmt = lline [:m.end ()] data = lline [m.end():].strip () else: fmt = "" data = line_buf.strip () # in extended mode, adjust leading spaces of data lines so # that the first data line has none, and all other data lines # are aligned relatively to the first one; for lists, preserve # one leading space if opt_ext: start = cur_indent # number of unwanted leading spaces for s in ["*", "1.", "a.", "A.", "i.", "I."]: if data.startswith (s): start = start -1 # preserve 1 space data = " "*cur_indent+data # 'unstrip' the 1st line (w/o tbl fmt) data_lines = data.split ("\n") for i in range (len (data_lines)): data_lines [i] = data_lines [i] [start:] # del unwanted spaces data = ("\n").join (data_lines) # store cell lines_info.append ( (cur_indent, fmt, data) ) # ready for next line line_buf = "" last_indent = cur_indent # # generate table structure # table_fmt_buf = "" # decide whether row or column-oriented is_by_col = nb_indent_dec==0 and nb_indent_eq > 0 if opt_col: is_by_col = True if opt_row: is_by_col = False # generate a token base that does not occur in the source, and # that is MoinMoin neutral, and not an HTML sequence token_base = "token" src = "\n".join (lines) while src.find (token_base) >=0: # append some random letter token_base = token_base + LETTERS [random.randint (0, LETTERS_LEN-1)] # function to generate tokens mk_token = lambda i: "%s%i" % (token_base, i) # function to generate a cell, either with a token, or with raw # content, depending on whether we must interpret the content if opt_ext: mk_cell = lambda fmt, i, data: "||%s %s " % (fmt, mk_token (i)) else: mk_cell = lambda fmt, i, data: "||%s %s " % (fmt, data) # row-oriented structure: # the table flow is the same as regular MoinMoin tables, all we # have to do is detect the end of rows and generate end of lines if not is_by_col: indent = 0 line_index = 0 if not opt_ext: substitute_content = False for cur_indent, fmt, line in lines_info: # same or lower indent ? ==> new row: close previous and start new if cur_indent <= indent and len (table_fmt_buf): table_fmt_buf = table_fmt_buf +"||\n" # add cell table_fmt_buf = table_fmt_buf + mk_cell (fmt, line_index, line) indent = cur_indent line_index = line_index + 1 # close table if len (table_fmt_buf): table_fmt_buf = table_fmt_buf + "||" # column-oriented structure: # a bit more complicated; the cells must be reordered; we first # determine the coordinates of data and store them in a (pseudo) # table; then we generate the table structure, picking the right # data else: # determine coordinates and store data indent = -1 col, row = 0, 0 max_col, max_row = 0, 0 # will be needed to generate the table table = {} for index in range (len (lines_info)): cur_indent = lines_info [index] [0] if cur_indent == indent: # new row row = row + 1 if row > max_row: max_row = row else: # new column row = 1 col = col + 1 if col > max_col: max_col = col indent = cur_indent # store coordinates and data index table [col-1,row-1] = index # generate table for row in range (max_row): for col in range (max_col): if table.has_key ((col,row)): index = table [col,row] fmt, line = lines_info [index] [1:] table_fmt_buf = table_fmt_buf + mk_cell (fmt, index, line) else: table_fmt_buf = table_fmt_buf + "|| " # empty cell table_fmt_buf = table_fmt_buf +"||\n" # # final table generation # # emit debug if opt_dbg or opt_dbgf: if opt_dbgf: if substitute_content: data = "\nData:\n" for i in range (len (lines_info)): line = lines_info [i] [2] data = data + "%d: [%s]\n" % (i, line) else: data = "" output = "{{{\nSource:\n{{ %s\n%s\n}}\n" \ "\nTable:\n%s\n" \ "%s}}}" % \ (bang, "\n".join (lines), table_fmt_buf, data) else: output = "{{{\n%s\n}}}" % "\n".join (lines) parser = wiki.Parser (output, request) parser.format (formatter) # generate html for table structure, generate each cell, then # merge them if substitute_content: # gen table struct html_buf = format (table_fmt_buf, request, formatter) # gen cells contents and merge in table for i in range (len (lines_info)): line = lines_info [i] [2] token = mk_token (i) content = format (line, request, formatter) html_buf = html_buf.replace (token, content, 1) # proudly emit the result request.write(html_buf) # emit html-formatted content # we have the table in MoinMoin source format, just HTML-convert it else: output = "%s\n" % table_fmt_buf parser = wiki.Parser (output, request) parser.format (formatter) # done! return def format (src_text, request, formatter): # parse the text (in wiki source format) and make HTML, # after diverting sys.stdout to a string str_out = cStringIO.StringIO () # create str to collect output request.redirect (str_out) # divert output to that string # parse this line wiki.Parser (src_text, request).format (formatter) request.redirect () # restore output return str_out.getvalue ().strip () # return what was generated