Attachment 'IndentTable.py'

Download

   1 """
   2 MoinMoin - IndentTable.py processor
   3 
   4 Processor for turning indented lists of data into tables.
   5 
   6 With classical MoinMoin tables syntax:
   7 * the source can become pretty unreadable when cells contents grow
   8 * it is difficult to handle multiple lines
   9 * it is impossible to have lists (other than by including a page
  10   in a cell)
  11 
  12 This processor is meant to correct the above problems, by the
  13 means of an indented list:
  14 * each cell consists of one line
  15 * its indent determines its position in the table
  16 * one line can be continued on the next line by means of '\' at the
  17   end; this helps keeping long contents readable
  18 * macros accepted in regular tables are accepted too, e.g [[BR]]
  19 * all regular table formatting (formats in <>'s, cell spanning)
  20   are supported
  21 * optionally, the cell contents can be parsed, allowing to have
  22   any kind of lists, headings, etc.
  23 
  24 The price to pay for the increased power and readability of the
  25 source, versus the regular table syntax, is the loss of the 2D
  26 matrix layout.
  27 
  28 @copyright: Pascal Bauermeister <pascal DOT bauermeister AT hispeed DOT ch>
  29 @license: GPL
  30 
  31 Updates:
  32 * [v0.0.1] Pascal - Thu Jul 29 15:41:25 CEST 2004
  33   - initial release
  34 
  35 -------------------------------------------------------------------------------
  36 
  37 Usage:
  38 {{{#!IndentTable OPTIONS
  39 indented data
  40 }}}
  41 
  42 Options:
  43   debug         insert debug info in the output
  44   debug=full    insert more debug info in the output
  45   +, extended   parse each cell content (useful for lists and headings)
  46   -             force row mode
  47   |             force column mode
  48 
  49 -------------------------------------------------------------------------------
  50 
  51 Samples:
  52 
  53 {{{#!IndentTable
  54 1                               1
  55 2                       ==>     2
  56 3                               3
  57 4                               4
  58 }}}
  59 
  60 {{{#!IndentTable
  61 1
  62  2                      ==>     1 2 3 4
  63   3
  64    4
  65 }}}
  66 
  67 {{{#!IndentTable
  68 1
  69  2                      ==>     1 2 3
  70   3                                 4
  71   4
  72 }}}
  73 
  74 {{{#!IndentTable -
  75 1
  76  2                      ==>     1 2 3
  77   3                             4
  78   4
  79 }}}
  80 
  81 {{{#!IndentTable
  82 1
  83  2                      ==>     1 2 3
  84   3                             4
  85  4
  86 }}}
  87 
  88 {{{#!IndentTable
  89 1
  90  2                      ==>     1 2
  91 3                               3 4
  92  4
  93 }}}
  94 
  95 {{{#!IndentTable
  96 1
  97 2                       ==>     1 3 4
  98  3                              2
  99   4
 100 }}}
 101 
 102 {{{#!IndentTable -
 103 1
 104 2                       ==>     1
 105  3                              2 3 4
 106   4
 107 }}}
 108 
 109 {{{#!IndentTable
 110 1
 111 2                       ==>     1 3 5
 112  3                              2 4 6
 113  4
 114   5
 115   6
 116 }}}
 117 
 118 {{{ #!IndentTable +
 119 <width='30%'>1
 120  <width='30%'>2
 121   <width='30%'>3
 122 4                       ==>     1            2            3
 123  <|2(>== Cool ! ==\
 124  [[SystemInfo]]\                4  == Cool! ==            6
 125  Terrific, isn't it ?              <system>     
 126   6                             7  <information>          9
 127 7                                  Terrific, isn't it ?
 128   9                             10           11           12
 129 10
 130  11
 131   12
 132 
 133 }}
 134 
 135 {{{#!IndentTable +
 136   A1                    ==> +----+-----------------+------------+--+
 137     B1                      |A1  |B1               |    C1+D1   |  |
 138       ||C1+D1               +----+-----------------+------------+--+
 139   A2                        |    |                 |C2: Bullets:|  |
 140     B2                      |    |                 |  * bullet 1|D2|
 141       C2: Bullets: \        |A2  |B2               |  * bullet 2|  |
 142         * bullet 1 \        |    |                 |end of cell |  |
 143         * bullet 2 \        +----+-----------------+------------+--+
 144       end of cell           |A3  | a. (B3) numberes|C3          |  |
 145         D2                  |    | b. numbered item|            |  |
 146   A3                        +----+-----------------+------------+--+
 147     a. (B3) numbers \       |(A4)|B4               |            |  |
 148     a. numbered item        +----+-----------------+------------+--+
 149       C3
 150   '''''' (A4)
 151     B4
 152 ## You find this list unreadable ?  try to do the same with just ||'s !
 153 }}}
 154 """
 155 
 156 from MoinMoin.parser import wiki
 157 import cStringIO, re, string, random
 158 
 159 LETTERS = string.ascii_lowercase
 160 LETTERS_LEN = len (LETTERS)
 161 
 162 LIST_HEADERS = ["*", "1.", "a.", "A.", "i.", "I."]
 163 
 164 COL_FMT_RE1 = re.compile("\\|*<[^>]*>")
 165 COL_FMT_RE2 = re.compile("\\|*") # FIXME: unify these two regexes
 166 
 167 def process (request, formatter, lines):
 168 
 169     # default options values
 170     opt_dbg = False
 171     opt_dbgf = False # verbose debug
 172     opt_ext = False
 173     opt_row = False
 174     opt_col = False # not sure this is really useful...
 175     substitute_content = True
 176 
 177     # parse bangpath for arguments
 178     bang = lines [0]
 179     for arg in bang.split () [1:]:
 180         if   arg=="debug":      opt_dbg = True
 181         if   arg=="debug=full": opt_dbgf = True
 182         elif arg=="extended":   opt_ext = True
 183         elif arg=="+":          opt_ext = True
 184         elif arg=="-":          opt_row = True
 185         elif arg=="|":          opt_col = True
 186 
 187     # remove bang path
 188     del lines [0]
 189 
 190     #
 191     # collect src lines
 192     #
 193     lines_info = []
 194     line_buf = ""
 195     last_indent = -1
 196     nb_indent_eq, nb_indent_dec = 0, 0
 197     for line in lines:
 198         # skip comments
 199         if line.lstrip ().startswith ("##"): continue
 200 
 201         # handle unterminated lines
 202         if line.strip ().endswith ("\\"):
 203             line_buf = line_buf + line.rstrip ('\\ ')
 204             if opt_ext: line_buf = line_buf + "\n"
 205             continue # continue, to finish line
 206         
 207         # append current line to any previously unterminated line
 208         else: line_buf = line_buf + line
 209 
 210         # skip empty lines
 211         if len (line_buf.strip ()) == 0: continue
 212 
 213         # calculate indent
 214         lline = line_buf.lstrip ()
 215         cur_indent = len (line_buf) - len (lline)
 216         if cur_indent == last_indent: nb_indent_eq = nb_indent_eq + 1
 217         if cur_indent < last_indent:  nb_indent_dec = nb_indent_dec + 1
 218 
 219         # detect table formatting
 220         m = COL_FMT_RE1.match (lline) or COL_FMT_RE2.match (lline)
 221         if m and m.start() == 0:
 222             fmt = lline [:m.end ()]
 223             data = lline [m.end():].strip ()
 224         else:
 225             fmt = ""
 226             data = line_buf.strip ()
 227 
 228         # in extended mode, adjust leading spaces of data lines so
 229         # that the first data line has none, and all other data lines
 230         # are aligned relatively to the first one; for lists, preserve
 231         # one leading space
 232         if opt_ext:
 233             start = cur_indent # number of unwanted leading spaces
 234             for s in ["*", "1.", "a.", "A.", "i.", "I."]:
 235                 if data.startswith (s): start = start -1 # preserve 1 space
 236             data = " "*cur_indent+data # 'unstrip' the 1st line (w/o tbl fmt)
 237             data_lines = data.split ("\n")
 238             for i in range (len (data_lines)):
 239                 data_lines [i] = data_lines [i] [start:] # del unwanted spaces
 240             data = ("\n").join (data_lines)
 241             
 242 
 243         # store cell
 244         lines_info.append ( (cur_indent, fmt, data) )
 245 
 246         # ready for next line
 247         line_buf = ""
 248         last_indent = cur_indent
 249 
 250     #
 251     # generate table structure
 252     #
 253     table_fmt_buf = ""
 254 
 255     # decide whether row or column-oriented
 256     is_by_col = nb_indent_dec==0 and nb_indent_eq > 0
 257     if opt_col: is_by_col = True
 258     if opt_row: is_by_col = False
 259 
 260     # generate a token base that does not occur in the source, and
 261     # that is MoinMoin neutral, and not an HTML sequence
 262     token_base = "token"
 263     src = "\n".join (lines)
 264     while src.find (token_base) >=0:
 265         # append some random letter
 266         token_base = token_base + LETTERS [random.randint (0, LETTERS_LEN-1)]
 267     
 268     # function to generate tokens
 269     mk_token = lambda i: "%s%i" % (token_base, i)
 270 
 271     # function to generate a cell, either with a token, or with raw
 272     # content, depending on whether we must interpret the content
 273     if opt_ext: mk_cell = lambda fmt, i, data: "||%s %s " % (fmt, mk_token (i))
 274     else:       mk_cell = lambda fmt, i, data: "||%s %s " % (fmt, data)
 275 
 276     # row-oriented structure:
 277     #  the table flow is the same as regular MoinMoin tables, all we
 278     #  have to do is detect the end of rows and generate end of lines
 279     if not is_by_col:
 280         indent = 0
 281         line_index = 0
 282         if not opt_ext: substitute_content = False
 283         for cur_indent, fmt, line in lines_info:
 284             # same or lower indent ?  ==> new row: close previous and start new
 285             if cur_indent <= indent and len (table_fmt_buf):
 286                 table_fmt_buf = table_fmt_buf +"||\n"
 287 
 288             # add cell
 289             table_fmt_buf = table_fmt_buf + mk_cell (fmt, line_index, line)
 290 
 291             indent = cur_indent
 292             line_index = line_index + 1
 293 
 294         # close table
 295         if len (table_fmt_buf): table_fmt_buf = table_fmt_buf + "||"
 296 
 297     # column-oriented structure:
 298     #  a bit more complicated; the cells must be reordered; we first
 299     #  determine the coordinates of data and store them in a (pseudo)
 300     #  table; then we generate the table structure, picking the right
 301     #  data
 302     else:
 303         # determine coordinates and store data
 304         indent = -1
 305         col, row = 0, 0
 306         max_col, max_row = 0, 0 # will be needed to generate the table
 307         table = {}
 308         for index in range (len (lines_info)):
 309             cur_indent = lines_info [index] [0]
 310             if cur_indent == indent:
 311                 # new row
 312                 row = row + 1
 313                 if row > max_row: max_row = row
 314             else:
 315                 # new column
 316                 row = 1
 317                 col = col + 1
 318                 if col > max_col: max_col = col
 319                 indent = cur_indent
 320             # store coordinates and data index
 321             table [col-1,row-1] = index
 322 
 323         # generate table
 324         for row in range (max_row):
 325             for col in range (max_col):
 326                 if table.has_key ((col,row)):
 327                     index = table [col,row]
 328                     fmt, line = lines_info [index] [1:]
 329                     table_fmt_buf = table_fmt_buf + mk_cell (fmt, index, line)
 330                 else:
 331                     table_fmt_buf = table_fmt_buf + "|| " # empty cell
 332             table_fmt_buf = table_fmt_buf +"||\n"                
 333 
 334     #
 335     # final table generation
 336     #
 337 
 338     # emit debug
 339     if opt_dbg or opt_dbgf:
 340         if opt_dbgf:
 341             if substitute_content:
 342                 data = "\nData:\n"
 343                 for i in range (len (lines_info)):
 344                     line = lines_info [i] [2]
 345                     data = data + "%d: [%s]\n" % (i, line)
 346             else: data = ""
 347             output = "{{{\nSource:\n{{ %s\n%s\n}}\n" \
 348                      "\nTable:\n%s\n" \
 349                      "%s}}}" % \
 350                      (bang, "\n".join (lines), table_fmt_buf, data)
 351 
 352         else: output = "{{{\n%s\n}}}" % "\n".join (lines)
 353             
 354         parser = wiki.Parser (output, request)
 355         parser.format (formatter)
 356         
 357     # generate html for table structure, generate each cell, then
 358     # merge them
 359     if substitute_content:
 360         # gen table struct
 361         html_buf = format (table_fmt_buf, request, formatter)
 362         # gen cells contents and merge in table
 363         for i in range (len (lines_info)):
 364             line = lines_info [i] [2]
 365             token = mk_token (i)
 366             content = format (line, request, formatter)
 367             html_buf = html_buf.replace (token, content, 1)
 368         # proudly emit the result
 369         request.write(html_buf) # emit html-formatted content
 370 
 371     # we have the table in MoinMoin source format, just HTML-convert it
 372     else:
 373         output = "%s\n" % table_fmt_buf
 374         parser = wiki.Parser (output, request)
 375         parser.format (formatter)
 376 
 377     # done!
 378     return
 379 
 380 
 381 def format (src_text, request, formatter):
 382     # parse the text (in wiki source format) and make HTML,
 383     # after diverting sys.stdout to a string
 384     str_out = cStringIO.StringIO ()     # create str to collect output
 385     request.redirect (str_out)          # divert output to that string
 386     # parse this line
 387     wiki.Parser (src_text, request).format (formatter)
 388     request.redirect ()                 # restore output
 389     return str_out.getvalue ().strip () # return what was generated

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2004-05-05 21:34:20, 10.3 KB) [[attachment:AbcMusic.py]]
  • [get | view] (2004-04-15 07:19:38, 3.4 KB) [[attachment:DataLanguage.py]]
  • [get | view] (2004-04-15 20:23:33, 2.5 KB) [[attachment:GANTT-1.2.1.py]]
  • [get | view] (2003-12-07 18:15:55, 2.4 KB) [[attachment:GANTT.py]]
  • [get | view] (2004-07-29 15:04:28, 12.1 KB) [[attachment:IndentTable.py]]
  • [get | view] (2004-10-05 13:12:16, 10.6 KB) [[attachment:MySQL.py]]
  • [get | view] (2004-03-27 18:55:57, 3.7 KB) [[attachment:SimpleTable.py]]
  • [get | view] (2003-12-07 18:15:55, 1.5 KB) [[attachment:TextOnRight.py]]
  • [get | view] (2004-09-17 06:53:46, 3.0 KB) [[attachment:awktable-1.2.3.py]]
  • [get | view] (2004-10-28 13:55:04, 1.0 KB) [[attachment:colorer.py]]
  • [get | view] (2004-11-04 00:26:37, 1.4 KB) [[attachment:csv_python_module.diff]]
  • [get | view] (2004-04-21 18:29:38, 0.2 KB) [[attachment:html.py]]
  • [get | view] (2004-08-16 10:59:24, 4.8 KB) [[attachment:latex-1.2.3.py]]
  • [get | view] (2004-08-07 07:36:59, 3.9 KB) [[attachment:latex-cygwin.py]]
  • [get | view] (2004-04-09 17:05:33, 2.9 KB) [[attachment:latex.1.2.1.py]]
  • [get | view] (2004-02-29 16:50:13, 2.1 KB) [[attachment:latex.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.