diff options
Diffstat (limited to 'release-notes/markdown_generator.py')
-rw-r--r-- | release-notes/markdown_generator.py | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/release-notes/markdown_generator.py b/release-notes/markdown_generator.py new file mode 100644 index 0000000..b0f84eb --- /dev/null +++ b/release-notes/markdown_generator.py @@ -0,0 +1,264 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2018 Danxue Huang (danxue.huang@gmail.com) +# Copyright 2022 Chris Johns (chris@contemporary.software) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import os +import re + + +class MarkdownGenerator: + + def __init__(self, line_width=78): + self.content = '' + self.line_width = line_width + + @staticmethod + def _max_len(lst): + max_len = 0 + for e in lst: + if len(e) > max_len or (len(e) == 0 and max_len < len(' ')): + max_len = len(e) if len(e) > 0 else len(' ') + return max_len + + def gen_bullet_point(self, text): + self.content += '* ' + self.wrap_line( + self._convert_to_unicode_str(text), self.line_width) + os.linesep + + def gen_line(self, text): + self.content += self.wrap_line(self._convert_to_unicode_str(text), + self.line_width) + os.linesep + + def gen_unwrapped_line(self, text, is_raw_text=True): + self.content += text + self.content += (' ' + os.linesep if is_raw_text else '<br />') + + def gen_heading(self, text, level, anchor=None): + self.content += os.linesep + \ + '#' * level + ' ' + \ + self._convert_to_unicode_str(text) + if anchor is not None: + self.content += ' {#' + anchor + '}' + self.content += os.linesep * 2 + + def gen_wrapped_table(self, header, rows, max_num_cols=4): + num_cols = len(header) + i = 0 + if num_cols > max_num_cols: + while i < num_cols: + self.gen_table( + list(header)[i:i + max_num_cols], + [list(row)[i:i + max_num_cols] for row in rows], + ) + self.gen_line(os.linesep) + i += max_num_cols + else: + self.gen_table(header, rows, align='left') + + def gen_page_break(self): + self.gen_line('') + self.gen_line('') + self.gen_line('<div class="new-page"></div>') + self.gen_line('') + + def gen_line_break(self): + self.gen_line('') + self.gen_line('') + self.gen_line('<br />') + self.gen_line('') + + def gen_raw(self, content): + self.content += content + + def gen_line_block(self, text): + if len(text.strip()) > 0: + self.content += os.linesep * 2 + '<div class="line-block">' + os.linesep + self.content += text + self.content += os.linesep * 2 + '</div>' + os.linesep + return + lines = text.split(os.linesep) + code_block = False + lb_lines = [] + for l in lines: + if l.startswith('```'): + code_block = not code_block + else: + if code_block: + lb_lines += [' ' + l] + else: + lb_lines += ['| ' + l] + self.content += os.linesep + os.linesep.join(lb_lines) + os.linesep + + def gen_division_open(self, name): + self.gen_line('') + self.gen_line('<div class="%s">' % (name)) + self.gen_line('') + + def gen_division_close(self): + self.gen_line('') + self.gen_line('</div>') + self.gen_line('') + + def gen_unordered_lists(self, items, level=0): + md = [] + for i in items: + if isinstance(i, list): + md += self.gen_unordered_lists(i, level + 1) + else: + md += ['%s* %s' % (' ' * level, i)] + return os.linesep.join(md) + + def gen_ordered_lists(self, items, level=0): + md = [] + for i in items: + if isinstance(i, list): + md += self.gen_unordered_lists(i, level + 1) + else: + md += ['%s#. %s' % (' ' * level, i)] + return os.linesep.join(md) + + def gen_table(self, header, rows, align='left', sort_by=None): + rows = [[self._convert_to_unicode_str(r) for r in row] for row in rows] + if header is None: + cols = len(rows[0]) + else: + header = [self._convert_to_unicode_str(h) for h in header] + cols = len(header) + if isinstance(align, str): + align = [align] * cols + else: + if len(align) < cols: + align += ['left'] * (cols - len(align)) + for c in range(0, len(align)): + if align[c] not in ['left', 'right', 'center']: + raise RuntimeError('invalid table alignment:' + a) + align[c] = { + 'left': ('%-*s ', 1), + 'right': (' %*s', 1), + 'center': (' %-*s ', 2) + }[align[c]] + if isinstance(sort_by, str): + if header is None: + sort_by = None + else: + if sort_by not in header: + sort_by = None + else: + sort_by = header.index(sort_by) + if sort_by is None: + sort_col = 0 + else: + sort_col = sort_by + ordered = [(k, i) + for i, k in enumerate([row[sort_col] for row in rows])] + if sort_by is not None: + ordered = sorted(ordered, key=lambda k: k[0]) + col_sizes = [] + if header is None: + col_sizes = [0] * cols + else: + for hdr in header: + col_sizes += [len(hdr)] + for c in range(0, cols): + col_max = self._max_len([row[c] for row in rows]) + if col_sizes[c] < col_max: + col_sizes[c] = col_max + line_len = 0 + for size in col_sizes: + line_len += size + line = [] + if header is not None: + for c in range(0, cols): + line += [align[c][0] % (col_sizes[c], header[c])] + self.content += ' '.join(line) + os.linesep + line = [] + for c in range(0, cols): + line += ['-' * (col_sizes[c] + align[c][1])] + table_line = ' '.join(line) + os.linesep + self.content += table_line + for o in ordered: + row = rows[o[1]] + line = [] + if len(col_sizes) != len(row): + raise RuntimeError('header cols and row cols do not match') + for c in range(0, len(row)): + line += [ + align[c][0] % + (col_sizes[c], row[c] if len(row[c]) > 0 else ' ') + ] + self.content += ' '.join(line) + os.linesep + if header is None: + self.content += table_line + + def gen_raw_text(self, formatted_text): + self.content += os.linesep + formatted_text + os.linesep + + @staticmethod + def gen_html_esc(text): + for ch, esc in [('_', '_'), ('*', '*')]: + text = text.replace(ch, esc) + return text + + @staticmethod + def gen_anchor(text): + return '[' + text + ']: #' + text + ' ' + + @staticmethod + def gen_bold(text): + return '**' + MarkdownGenerator.gen_html_esc(text) + '**' + + @staticmethod + def gen_topic(text): + return '<div class="topic">' + os.linesep + text + os.linesep + '</div>' + + @staticmethod + def gen_hyperlink(text, link): + return '[' + text + ']' + '(' + link + ')' + + @staticmethod + def wrap_line(line, width, is_raw_text=False): + i = 0 + str_list = [] + while i < len(line): + str_list.append(line[i:i + width]) + i += width + return (' \n' if is_raw_text else '<br />').join(str_list) + + def gen_horizontal_line(self): + self.content += os.linesep + '--------' + os.linesep + + @staticmethod + def _convert_to_unicode_str(text): + try: + return str(text) + except UnicodeEncodeError: + if isinstance(text, unicode): + return text + else: + return unicode(text, "utf-8") |