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.You are not allowed to attach a file to this page.