Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 1 | # Copyright (c) 2016 Comcast Cable Communications Management, LLC. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at: |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | # Generation template class |
| 16 | |
Paul Vinciguerra | 464e5e0 | 2019-11-01 15:07:32 -0400 | [diff] [blame] | 17 | import html.parser |
| 18 | import json |
| 19 | import logging |
| 20 | import os |
| 21 | import sys |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 22 | import re |
Paul Vinciguerra | 464e5e0 | 2019-11-01 15:07:32 -0400 | [diff] [blame] | 23 | |
| 24 | import jinja2 |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 25 | |
| 26 | # Classes register themselves in this dictionary |
| 27 | """Mapping of known processors to their classes""" |
| 28 | siphons = {} |
| 29 | |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 30 | """Mapping of known output formats to their classes""" |
| 31 | formats = {} |
| 32 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 33 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 34 | class Siphon(object): |
Paul Vinciguerra | 464e5e0 | 2019-11-01 15:07:32 -0400 | [diff] [blame] | 35 | """Generate rendered output for siphoned data.""" |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 36 | |
| 37 | # Set by subclasses |
| 38 | """Our siphon name""" |
| 39 | name = None |
| 40 | |
| 41 | # Set by subclasses |
| 42 | """Name of an identifier used by this siphon""" |
| 43 | identifier = None |
| 44 | |
| 45 | # Set by subclasses |
| 46 | """The pyparsing object to use to parse with""" |
| 47 | _parser = None |
| 48 | |
| 49 | """The input data""" |
| 50 | _cmds = None |
| 51 | |
| 52 | """Group key to (directory,file) mapping""" |
| 53 | _group = None |
| 54 | |
| 55 | """Logging handler""" |
| 56 | log = None |
| 57 | |
| 58 | """Directory to look for siphon rendering templates""" |
| 59 | template_directory = None |
| 60 | |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 61 | """Directory to output parts in""" |
| 62 | outdir = None |
| 63 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 64 | """Template environment, if we're using templates""" |
| 65 | _tplenv = None |
| 66 | |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 67 | def __init__(self, template_directory, format, outdir, repository_link): |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 68 | super(Siphon, self).__init__() |
| 69 | self.log = logging.getLogger("siphon.process.%s" % self.name) |
| 70 | |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 71 | # Get our output format details |
| 72 | fmt_klass = formats[format] |
| 73 | fmt = fmt_klass() |
| 74 | self._format = fmt |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 75 | |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 76 | # Sort out the template search path |
| 77 | def _tpldir(name): |
| 78 | return os.sep.join((template_directory, fmt.name, name)) |
| 79 | |
| 80 | self.template_directory = template_directory |
| 81 | searchpath = [ |
| 82 | _tpldir(self.name), |
| 83 | _tpldir("default"), |
| 84 | ] |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 85 | self.outdir = outdir |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 86 | loader = jinja2.FileSystemLoader(searchpath=searchpath) |
| 87 | self._tplenv = jinja2.Environment( |
| 88 | loader=loader, |
| 89 | trim_blocks=True, |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 90 | autoescape=False, |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 91 | keep_trailing_newline=True, |
| 92 | ) |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 93 | |
| 94 | # Convenience, get a reference to the internal escape and |
Paul Vinciguerra | 464e5e0 | 2019-11-01 15:07:32 -0400 | [diff] [blame] | 95 | # unescape methods in html.parser. These then become |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 96 | # available to templates to use, if needed. |
Paul Vinciguerra | 464e5e0 | 2019-11-01 15:07:32 -0400 | [diff] [blame] | 97 | self._h = html.parser.HTMLParser() |
| 98 | self.escape = html.escape |
| 99 | self.unescape = html.unescape |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 100 | |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 101 | # TODO: customize release |
| 102 | self.repository_link = repository_link |
| 103 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 104 | # Output renderers |
| 105 | |
| 106 | """Returns an object to be used as the sorting key in the item index.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 107 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 108 | def index_sort_key(self, group): |
| 109 | return group |
| 110 | |
| 111 | """Returns a string to use as the header at the top of the item index.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 112 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 113 | def index_header(self): |
| 114 | return self.template("index_header") |
| 115 | |
| 116 | """Returns the string fragment to use for each section in the item |
| 117 | index.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 118 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 119 | def index_section(self, group): |
| 120 | return self.template("index_section", group=group) |
| 121 | |
| 122 | """Returns the string fragment to use for each entry in the item index.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 123 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 124 | def index_entry(self, meta, item): |
| 125 | return self.template("index_entry", meta=meta, item=item) |
| 126 | |
| 127 | """Returns an object, typically a string, to be used as the sorting key |
| 128 | for items within a section.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 129 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 130 | def item_sort_key(self, item): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 131 | return item["name"] |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 132 | |
| 133 | """Returns a key for grouping items together.""" |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 134 | |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 135 | def group_key(self, directory, file, macro, name): |
| 136 | _global = self._cmds["_global"] |
| 137 | |
| 138 | if file in _global and "group_label" in _global[file]: |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 139 | self._group[file] = (directory, file) |
| 140 | return file |
| 141 | |
| 142 | self._group[directory] = (directory, None) |
| 143 | return directory |
| 144 | |
| 145 | """Returns a key for identifying items within a grouping.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 146 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 147 | def item_key(self, directory, file, macro, name): |
| 148 | return name |
| 149 | |
| 150 | """Returns a string to use as the header when rendering the item.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 151 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 152 | def item_header(self, group): |
| 153 | return self.template("item_header", group=group) |
| 154 | |
| 155 | """Returns a string to use as the body when rendering the item.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 156 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 157 | def item_format(self, meta, item): |
| 158 | return self.template("item_format", meta=meta, item=item) |
| 159 | |
| 160 | """Returns a string to use as the label for the page reference.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 161 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 162 | def page_label(self, group): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 163 | return "_".join((self.name, self.sanitize_label(group))) |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 164 | |
| 165 | """Returns a title to use for a page.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 166 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 167 | def page_title(self, group): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 168 | _global = self._cmds["_global"] |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 169 | (directory, file) = self._group[group] |
| 170 | |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 171 | if file and file in _global and "group_label" in _global[file]: |
| 172 | return _global[file]["group_label"] |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 173 | |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 174 | if directory in _global and "group_label" in _global[directory]: |
| 175 | return _global[directory]["group_label"] |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 176 | |
| 177 | return directory |
| 178 | |
| 179 | """Returns a string to use as the label for the section reference.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 180 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 181 | def item_label(self, group, item): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 182 | return "__".join((self.name, item)) |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 183 | |
| 184 | """Label sanitizer; for creating Doxygen references""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 185 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 186 | def sanitize_label(self, value): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 187 | return value.replace(" ", "_").replace("/", "_").replace(".", "_") |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 188 | |
| 189 | """Template processor""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 190 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 191 | def template(self, name, **kwargs): |
Paul Vinciguerra | 464e5e0 | 2019-11-01 15:07:32 -0400 | [diff] [blame] | 192 | tpl = self._tplenv.get_template(name + self._format.extension) |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 193 | return tpl.render(this=self, **kwargs) |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 194 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 195 | # Processing methods |
| 196 | |
| 197 | """Parse the input file into a more usable dictionary structure.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 198 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 199 | def load_json(self, files): |
| 200 | self._cmds = {} |
| 201 | self._group = {} |
| 202 | |
| 203 | line_num = 0 |
| 204 | line_start = 0 |
| 205 | for filename in files: |
| 206 | filename = os.path.relpath(filename) |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 207 | self.log.info('Parsing items in file "%s".' % filename) |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 208 | data = None |
| 209 | with open(filename, "r") as fd: |
| 210 | data = json.load(fd) |
| 211 | |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 212 | self._cmds["_global"] = data["global"] |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 213 | |
| 214 | # iterate the items loaded and regroup it |
| 215 | for item in data["items"]: |
| 216 | try: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 217 | o = self._parser.parse(item["block"]) |
Paul Vinciguerra | 464e5e0 | 2019-11-01 15:07:32 -0400 | [diff] [blame] | 218 | except Exception: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 219 | self.log.error( |
| 220 | "Exception parsing item: %s\n%s" |
| 221 | % ( |
| 222 | json.dumps(item, separators=(",", ": "), indent=4), |
| 223 | item["block"], |
| 224 | ) |
| 225 | ) |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 226 | raise |
| 227 | |
| 228 | # Augment the item with metadata |
| 229 | o["meta"] = {} |
| 230 | for key in item: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 231 | if key == "block": |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 232 | continue |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 233 | o["meta"][key] = item[key] |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 234 | |
| 235 | # Load some interesting fields |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 236 | directory = item["directory"] |
| 237 | file = item["file"] |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 238 | macro = o["macro"] |
| 239 | name = o["name"] |
| 240 | |
| 241 | # Generate keys to group items by |
| 242 | group_key = self.group_key(directory, file, macro, name) |
| 243 | item_key = self.item_key(directory, file, macro, name) |
| 244 | |
| 245 | if group_key not in self._cmds: |
| 246 | self._cmds[group_key] = {} |
| 247 | |
| 248 | self._cmds[group_key][item_key] = o |
| 249 | |
| 250 | """Iterate over the input data, calling render methods to generate the |
| 251 | output.""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 252 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 253 | def process(self, out=None): |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 254 | if out is None: |
| 255 | out = sys.stdout |
| 256 | |
| 257 | # Accumulated body contents |
| 258 | contents = "" |
| 259 | |
| 260 | # Write the header for this siphon type |
| 261 | out.write(self.index_header()) |
| 262 | |
| 263 | # Sort key helper for the index |
| 264 | def group_sort_key(group): |
| 265 | return self.index_sort_key(group) |
| 266 | |
| 267 | # Iterate the dictionary and process it |
| 268 | for group in sorted(self._cmds.keys(), key=group_sort_key): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 269 | if group.startswith("_"): |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 270 | continue |
| 271 | |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 272 | self.log.info( |
| 273 | 'Processing items in group "%s" (%s).' % (group, group_sort_key(group)) |
| 274 | ) |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 275 | |
| 276 | # Generate the section index entry (write it now) |
| 277 | out.write(self.index_section(group)) |
| 278 | |
| 279 | # Generate the item header (save for later) |
| 280 | contents += self.item_header(group) |
| 281 | |
| 282 | def item_sort_key(key): |
| 283 | return self.item_sort_key(self._cmds[group][key]) |
| 284 | |
| 285 | for key in sorted(self._cmds[group].keys(), key=item_sort_key): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 286 | self.log.debug( |
| 287 | '--- Processing key "%s" (%s).' % (key, item_sort_key(key)) |
| 288 | ) |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 289 | |
| 290 | o = self._cmds[group][key] |
| 291 | meta = { |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 292 | "directory": o["meta"]["directory"], |
| 293 | "file": o["meta"]["file"], |
| 294 | "macro": o["macro"], |
| 295 | "name": o["name"], |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 296 | "key": key, |
| 297 | "label": self.item_label(group, key), |
| 298 | } |
| 299 | |
| 300 | # Generate the index entry for the item (write it now) |
| 301 | out.write(self.index_entry(meta, o)) |
| 302 | |
| 303 | # Generate the item itself (save for later) |
| 304 | contents += self.item_format(meta, o) |
| 305 | |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 306 | page_name = self.separate_page_names(group) |
| 307 | if page_name != "": |
| 308 | path = os.path.join(self.outdir, page_name) |
| 309 | with open(path, "w+") as page: |
| 310 | page.write(contents) |
| 311 | contents = "" |
| 312 | |
Chris Luke | 90f52bf | 2016-09-12 08:55:13 -0400 | [diff] [blame] | 313 | # Deliver the accumulated body output |
| 314 | out.write(contents) |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 315 | |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 316 | def do_cliexstart(self, matchobj): |
| 317 | title = matchobj.group(1) |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 318 | title = " ".join(title.splitlines()) |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 319 | content = matchobj.group(2) |
| 320 | content = re.sub(r"\n", r"\n ", content) |
| 321 | return "\n\n.. code-block:: console\n\n %s\n %s\n\n" % (title, content) |
| 322 | |
| 323 | def do_clistart(self, matchobj): |
| 324 | content = matchobj.group(1) |
| 325 | content = re.sub(r"\n", r"\n ", content) |
| 326 | return "\n\n.. code-block:: console\n\n %s\n\n" % content |
| 327 | |
| 328 | def do_cliexcmd(self, matchobj): |
| 329 | content = matchobj.group(1) |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 330 | content = " ".join(content.splitlines()) |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 331 | return "\n\n.. code-block:: console\n\n %s\n\n" % content |
| 332 | |
| 333 | def process_list(self, matchobj): |
| 334 | content = matchobj.group(1) |
| 335 | content = self.reindent(content, 2) |
| 336 | return "@@@@%s\nBBBB" % content |
| 337 | |
| 338 | def process_special(self, s): |
| 339 | # ----------- markers to remove |
| 340 | s = re.sub(r"@cliexpar\s*", r"", s) |
| 341 | s = re.sub(r"@parblock\s*", r"", s) |
| 342 | s = re.sub(r"@endparblock\s*", r"", s) |
| 343 | s = re.sub(r"<br>", "", s) |
| 344 | # ----------- emphasis |
| 345 | # <b><em> |
| 346 | s = re.sub(r"<b><em>\s*", "``", s) |
| 347 | s = re.sub(r"\s*</b></em>", "``", s) |
| 348 | s = re.sub(r"\s*</em></b>", "``", s) |
| 349 | # <b> |
| 350 | s = re.sub(r"<b>\s*", "**", s) |
| 351 | s = re.sub(r"\s*</b>", "**", s) |
| 352 | # <code> |
| 353 | s = re.sub(r"<code>\s*", "``", s) |
| 354 | s = re.sub(r"\s*</code>", "``", s) |
| 355 | # <em> |
| 356 | s = re.sub(r"'?<em>\s*", r"``", s) |
| 357 | s = re.sub(r"\s*</em>'?", r"``", s) |
| 358 | # @c <something> |
| 359 | s = re.sub(r"@c\s(\S+)", r"``\1``", s) |
| 360 | # ----------- todos |
| 361 | s = re.sub(r"@todo[^\n]*", "", s) |
| 362 | s = re.sub(r"@TODO[^\n]*", "", s) |
| 363 | # ----------- code blocks |
| 364 | s = re.sub(r"@cliexcmd{(.+?)}", self.do_cliexcmd, s, flags=re.DOTALL) |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 365 | s = re.sub( |
| 366 | r"@cliexstart{(.+?)}(.+?)@cliexend", self.do_cliexstart, s, flags=re.DOTALL |
| 367 | ) |
Nathan Skrzypczak | 9ad39c0 | 2021-08-19 11:38:06 +0200 | [diff] [blame] | 368 | s = re.sub(r"@clistart(.+?)@cliend", self.do_clistart, s, flags=re.DOTALL) |
| 369 | # ----------- lists |
| 370 | s = re.sub(r"^\s*-", r"\n@@@@", s, flags=re.MULTILINE) |
| 371 | s = re.sub(r"@@@@(.*?)\n\n+", self.process_list, s, flags=re.DOTALL) |
| 372 | s = re.sub(r"BBBB@@@@", r"-", s) |
| 373 | s = re.sub(r"@@@@", r"-", s) |
| 374 | s = re.sub(r"BBBB", r"\n\n", s) |
| 375 | # ----------- Cleanup remains |
| 376 | s = re.sub(r"@cliexend\s*", r"", s) |
| 377 | return s |
| 378 | |
| 379 | def separate_page_names(self, group): |
| 380 | return "" |
| 381 | |
| 382 | # This push the given textblock <indent> spaces right |
| 383 | def reindent(self, s, indent): |
| 384 | ind = " " * indent |
| 385 | s = re.sub(r"\n", "\n" + ind, s) |
| 386 | return s |
| 387 | |
| 388 | # This aligns the given textblock left (no indent) |
| 389 | def noindent(self, s): |
| 390 | s = re.sub(r"\n[ \f\v\t]*", "\n", s) |
| 391 | return s |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 392 | |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 393 | |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 394 | class Format(object): |
Paul Vinciguerra | 464e5e0 | 2019-11-01 15:07:32 -0400 | [diff] [blame] | 395 | """Output format class""" |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 396 | |
| 397 | """Name of this output format""" |
| 398 | name = None |
| 399 | |
| 400 | """Expected file extension of templates that build this format""" |
| 401 | extension = None |
| 402 | |
| 403 | |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 404 | class FormatMarkdown(Format): |
Paul Vinciguerra | 464e5e0 | 2019-11-01 15:07:32 -0400 | [diff] [blame] | 405 | """Markdown output format""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 406 | |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 407 | name = "markdown" |
| 408 | extension = ".md" |
| 409 | |
Paul Vinciguerra | 464e5e0 | 2019-11-01 15:07:32 -0400 | [diff] [blame] | 410 | |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 411 | # Register 'markdown' |
| 412 | formats["markdown"] = FormatMarkdown |
| 413 | |
| 414 | |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 415 | class FormatItemlist(Format): |
Paul Vinciguerra | 464e5e0 | 2019-11-01 15:07:32 -0400 | [diff] [blame] | 416 | """Itemlist output format""" |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 417 | |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 418 | name = "itemlist" |
| 419 | extension = ".itemlist" |
| 420 | |
Paul Vinciguerra | 464e5e0 | 2019-11-01 15:07:32 -0400 | [diff] [blame] | 421 | |
Chris Luke | c3f92ad | 2016-10-05 15:45:19 -0400 | [diff] [blame] | 422 | # Register 'itemlist' |
| 423 | formats["itemlist"] = FormatItemlist |