Attachment 'chunks-1.0.py'

Download

   1 #-*- coding: utf-8 -*-
   2 '''
   3 MoinMoin - chunks parser, Version 1.0, 26.02.2019
   4 
   5 A simple parser that reads key:value pairs, a listing of chunks (sections of MoinMoin markup stored in attached files) and headings, then inserts them into the page either inline or with a parser section. The latter option allows for borders and background colours, with multiple key:value pairs being allocated to separate sections (with the same properties). The user can set options to (1) insert the chucks in parser section, (2) set chunk heading level, (3) prevent chunk heading insertion, (4) bump headings in chunks by a specified number of levels, (5) control heading numbering within the chunk, and (6) show only the markup for re-use with modifications, if desired, or for debugging purposes.
   6 
   7 The key:value list must be in the form `attachment:: heading` with a leading space (as MoinMoin dictionary entries). All non-conforming lines are ignored without warning.
   8 
   9 If the file is not a text mimetype, it is inserted using the in-built methods, either in-line or with a parser section.
  10 
  11 The parser is intended for page section use only. The parser name is followed by a list of keywords separated by spaces to specify any options required.
  12 
  13 Keywords and numerical arguments
  14 
  15 section : requests the chunks be inserted as parser sections. The default will be a section specified as `{#!wiki}`. Other sections specfication can be requested as `{{{specs}}}`, where `specs` is the requires section specifications (e.g. `{#!highlight python}` or `{#!wiki blue/solid}`). Headings in sections are not found by the TableOfContents macro.
  16 
  17 h-[0-6] : set chunk level heading. Default is level 2. A value of 0 is equivalent to "nohead" (if "nohead" is give it will take precedence).
  18 
  19 nohead : request that no headings be inserted even if they are given in the pair:value list.
  20 
  21 b-[0-5] : requests all headings in the chunk be bumped up one or more levels (as indicted by the number). Headings will not be bumped to a value above level 6. A value of 0 means no headings will be bumped (the default, if not requested)
  22 
  23 n-[on,off,0-7] : requests a temporay change in the state of the page pragma value for section -numbers.
  24 
  25 markup : shows the markup if you wish to re-use this with modification (assuming the default tabulation does not suit a specific purpose) or for debugging purposes.
  26 
  27 # : when inserted at the beginning of the heading, it overrides  insertion as a heading, and instead it is inserted as a comment.
  28 
  29 Defaults are inline insertion with headings with headings within the chunks unchanged.
  30 
  31 Keywords can appear in any order, however, the first instance of any option will be used if the option is repeated with conflicting choices.
  32 
  33 Usage
  34 
  35 {{{#!chunks [section ][`{specs}` ][h-[0-6] ][nohead ][b-[0-5] ][n-[on,off,0-7] ][markup ]
  36  chunk_file:: [#][heading]
  37  chunk_file:: [#][heading]
  38  ...
  39 }}}
  40 
  41 History
  42 
  43 Version 1.0 - 26.02.2019: initial version
  44 
  45 Developed with inspiration and code from:
  46 
  47 gallery.py parser - @copyright: 2018 Ian Riley
  48 tables.py parser - @copyright: 2012 Ian Riley
  49 figures.py parser - @copyright: 2011 Ian Riley
  50 keyval.py parser - @copyright: 2006 by Matt Cooper <macooper@vt.edu>
  51 sort.py parser - @copyright: 2005 ReimarBauer
  52 
  53 Copyright
  54 
  55 @copyright: 2019 Ian Riley <ian@riley.asia>
  56 
  57 License
  58 
  59 GNU GPL, see COPYING for details.
  60 '''
  61 
  62 import re
  63 from MoinMoin import wikiutil
  64 from MoinMoin.action import AttachFile
  65 from MoinMoin.Page import Page
  66 
  67 #from MoinMoin import log
  68 #logging = log.getLogger(__name__)
  69 
  70 Dependencies = ['time'] # do not cache - so heading numbering works. Why?
  71 generates_headings = True
  72 
  73 class Parser:
  74     parsername = 'chunks'
  75 
  76     def __init__(self, raw, request, **kw):
  77         self.raw = raw
  78         self.request = request
  79         self.form = request.form
  80         self._ = request.getText
  81         self.args = kw.get('format_args', '')
  82         self.section_numbers = 'off'
  83         self.parser = wikiutil.searchAndImportPlugin(self.request.cfg, "parser",
  84                                                      self.request.page.pi['format'])
  85 
  86     def format(self, formatter):
  87         empty = u''
  88         eol = u'\n'
  89         rtn = u'\r'
  90         space = u' '
  91         sep = u'::'
  92         sepspace = sep + space
  93         div = u'/'
  94         dot = u'.'
  95         ignore = u'#'
  96         hopt = u'h-'
  97         bopt = u'b-'
  98         nopt = u'n-'
  99         heading_max = u'======'
 100         specs = u'#!wiki' + eol
 101         on, off = (0, 1)
 102         parsing = (u"{{{", u"}}}", )
 103         specing = (u"{", u"}", )
 104         commenting = (u"/* ", u" */")
 105         chunk_head_depth_default = 2
 106         chunk_head_depth = chunk_head_depth_default
 107         heading_markup = u'%(level)s %(text)s %(level)s\n\n'
 108         bump_head_default = 0
 109         bump_head = bump_head_default
 110         heading_pattern = r'(^|\n)(=+ .+ =+)(\n|$)'
 111         heading_sub = r'\1%(bump)s\2%(bump)s\3'
 112         att = u'{{attachment:%(att)s}}' # used for missing and non-text mimetype attachments
 113         att_pattern = r'^{{attachment:.+}}$'
 114         headline = u''
 115         options, entries, chunks, results = ([], [], [], [], )
 116 
 117         # set options
 118         options = self.args.split(space)
 119         section = "section" in options
 120         nohead = "nohead" in options
 121         markup = "markup" in options
 122         for option in options:
 123             if option.startswith(hopt):
 124                 try:
 125                     chunk_head_depth = int(option.lstrip(hopt)) # will check this value later
 126                 except:
 127                     pass
 128                 break # do not look for any more width options
 129         for option in options:
 130             if option.startswith(bopt):
 131                 try:
 132                     bump_head = int(option.lstrip(bopt)) # will check this value later
 133                 except:
 134                     pass
 135                 break # do not look for any more bumping options
 136         for option in options:
 137             if option.startswith(nopt):
 138                 try:
 139                     self.section_numbers = option.lstrip(nopt) # no check, called parser does this
 140                 except:
 141                     pass
 142                 break # do not look for any more numbering options
 143         if (section and (specing[off] in self.args)):
 144             s = self.args.split(specing[off], 1)[0]
 145             if (specing[on] in s):
 146                 specs = s.split(specing[on])[-1] + eol
 147         del options
 148 
 149         # check heading requests in range or use default
 150         if (chunk_head_depth not in range(7)):
 151             chunk_head_depth = chunk_head_depth_default
 152         if (bump_head not in range(6)):
 153             bump_head = bump_head_default
 154 
 155         def get_attachment(request, page_name, source):
 156             if AttachFile.exists(request, page_name, source):
 157                 file_name = Page(request, page_name).getPagePath("attachments")
 158                 file_name = file_name + div + source
 159                 f = open(file_name,'r')
 160                 text = f.read()
 161                 f.close()
 162                 text = text.decode("utf-8").replace(rtn, empty)
 163                 # just let empty attachments pass
 164                 #if text == empty:
 165                 #    text = att % {'att': page_name + div + source}
 166             else:
 167                 text = att % {'att': page_name + div + source}
 168             return text
 169 
 170         # process the text provided
 171         lines = self.raw.split(eol)
 172         for line in lines:
 173             if line:
 174                 # handle chunck lines
 175                 if (line[0] == space) and ((sepspace in line) or line.endswith(sep)):
 176                     attachment, caption = (line.lstrip(space)).split(sep)
 177                     caption = caption.lstrip(space)
 178                     if (div not in attachment):
 179                         attachment = ignore + div + attachment
 180                     attpage, attfile = (attachment.rsplit(div, 1))
 181                     attpath = attpage
 182                     if (attpage == ignore):
 183                         attpage = formatter.page.page_name
 184                     attmime = (wikiutil.MimeType(filename=attfile)).major
 185                     attname, atttype = (attfile.rsplit(dot,1))
 186                     comment = False
 187                     if (caption.startswith(ignore)):
 188                         caption = commenting[on] + caption[1:] + commenting[off]
 189                         comment = True
 190                     elif (not nohead and (chunk_head_depth > 0) and len(caption) > 0):
 191                         caption = heading_markup % {'level': heading_max[0:chunk_head_depth],
 192                                                     'text': caption, }
 193                     else:
 194                         caption = empty
 195                     attnum = len(entries)
 196                     # store what we have, some is will be used, but some for possible checking only
 197                     entries.append([attnum, attpage, attpath, attfile, atttype, attmime, caption, ])
 198                     if (attmime == 'text'):
 199                        chunks.append(get_attachment(self.request, attpage, attfile))
 200                     else:
 201                        chunks.append(att % {'att': attpage + div + attfile})
 202 
 203         del lines
 204 
 205         # construct headline plus body list
 206         for entry in entries:
 207             headline = entry[6]
 208             body = chunks[entry[0]]
 209             if re.match(att_pattern, body):
 210                 section = False # do not put empty/missing attachments in sections
 211             else:
 212                 if (body[-1] != eol):
 213                     body = body + eol
 214                 if (bump_head > 0):
 215                     sub = heading_sub % {'bump': heading_max[0:bump_head], }
 216                     body = re.sub(heading_pattern, sub, body)
 217             if section:
 218                 body = parsing[on] + specs + body + parsing[off]
 219             results.append([headline, body, section ])
 220 
 221         del entries, chunks
 222 
 223         # escape markup if requested and send it
 224         if markup:
 225             content = parsing[on] + 'markup' + eol
 226             for headline, body, section in results:
 227                 content = content + headline + body
 228             content = content + eol + 'markup' + parsing[off]
 229             self.send(formatter, content, False)
 230         else:
 231             for headline, body, section in results:
 232                 self.send(formatter, headline, False)
 233                 self.send(formatter, body, section)
 234 
 235         del results
 236 
 237     def send(self, formatter, content, section=False):
 238         #if hasattr(formatter, 'collected_headings'): # TableOfContents macro visiting
 239         #    logging.debug("got collected headings: %s" % formatter.collected_headings)
 240         #    print(formatter.collected_headings)
 241 
 242         if section and hasattr(formatter, 'collected_headings'): # TableOfContents macro visiting
 243             return # do not waste time here
 244 
 245         if section:
 246             saved_section_numbers = formatter.request.getPragma('section-numbers', '')
 247             saved_show_number = formatter._show_section_numbers
 248             saved_counters = formatter.request._fmt_hd_counters
 249             formatter.request.setPragma('section-numbers', self.section_numbers)
 250             formatter._show_section_numbers = None
 251             formatter.request._fmt_hd_counters = []
 252 
 253         self.parser(content, self.request).format(formatter)
 254 
 255         if section:
 256             formatter.request.setPragma('section-numbers', saved_section_numbers)
 257             formatter._show_section_numbers = saved_show_number
 258             formatter.request._fmt_hd_counters = saved_counters

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