aboutsummaryrefslogtreecommitdiff
path: root/cros_utils/tiny_render.py
diff options
context:
space:
mode:
authorGeorge Burgess IV <gbiv@google.com>2020-04-27 14:36:05 -0700
committerGeorge Burgess <gbiv@chromium.org>2020-04-28 19:02:01 +0000
commit7b8508f497e3882a6cd3488878d7ee4ba4cf0c3a (patch)
tree54917d31996fa23a49700fce626c35b58a6a7392 /cros_utils/tiny_render.py
parent6acfe66ddbcd97bfd11d96781506b89dad34c2b2 (diff)
downloadtoolchain-utils-7b8508f497e3882a6cd3488878d7ee4ba4cf0c3a.tar.gz
cros_utils: import tiny_render + some (new) tests from google3
Purely plaintext emails are nice, but HTML can be way easier to deal with. `tiny_render` is a super simple way of being able to render text and HTML from the same structure, so we can send out descriptive emails accessible to people with various kinds of email clients. tiny_render was imported with slight modifications from //wireless/android/llvm/monitoring/commits/tiny_render.py . Tests are new. BUG=chromium:1046988 TEST=unittests Change-Id: Ic94064d0125d3e7655498c9c2b8501f1448420d6 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/2169009 Reviewed-by: Tiancong Wang <tcwang@google.com> Tested-by: George Burgess <gbiv@chromium.org>
Diffstat (limited to 'cros_utils/tiny_render.py')
-rw-r--r--cros_utils/tiny_render.py181
1 files changed, 181 insertions, 0 deletions
diff --git a/cros_utils/tiny_render.py b/cros_utils/tiny_render.py
new file mode 100644
index 00000000..629e7719
--- /dev/null
+++ b/cros_utils/tiny_render.py
@@ -0,0 +1,181 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""A super minimal module that allows rendering of readable text/html.
+
+Usage should be relatively straightforward. You wrap things you want to write
+out in some of the nice types defined here, and then pass the result to one of
+render_text_pieces/render_html_pieces.
+
+In HTML, the types should all nest nicely. In text, eh (nesting anything in
+Bold is going to be pretty ugly, probably).
+
+Lists and tuples may be used to group different renderable elements.
+
+Example:
+
+render_text_pieces([
+ Bold("Daily to-do list:"),
+ UnorderedList([
+ "Write code",
+ "Go get lunch",
+ ["Fix ", Bold("some"), " of the bugs in the aforementioned code"],
+ [
+ "Do one of the following:",
+ UnorderedList([
+ "Nap",
+ "Round 2 of lunch",
+ ["Look at ", Link("https://google.com/?q=memes", "memes")],
+ ]),
+ ],
+ "What a rough day; time to go home",
+ ]),
+])
+
+Turns into
+
+**Daily to-do list:**
+ - Write code
+ - Go get lunch
+ - Fix **some** of the bugs in said code
+ - Do one of the following:
+ - Nap
+ - Round 2 of lunch
+ - Look at memes
+ - What a rough day; time to go home
+
+...And similarly in HTML, though with an actual link.
+
+The rendering functions should never mutate your input.
+"""
+
+from __future__ import print_function
+
+import collections
+import html
+import typing as t
+
+Bold = collections.namedtuple('Bold', ['inner'])
+LineBreak = collections.namedtuple('LineBreak', [])
+Link = collections.namedtuple('Link', ['href', 'inner'])
+UnorderedList = collections.namedtuple('UnorderedList', ['items'])
+# Outputs different data depending on whether we're emitting text or HTML.
+Switch = collections.namedtuple('Switch', ['text', 'html'])
+
+line_break = LineBreak()
+
+# Note that these build up their values in a funky way: they append to a list
+# that ends up being fed to `''.join(into)`. This avoids quadratic string
+# concatenation behavior. Probably doesn't matter, but I care.
+
+# Pieces are really a recursive type:
+# Union[
+# Bold,
+# LineBreak,
+# Link,
+# List[Piece],
+# Tuple[...Piece],
+# UnorderedList,
+# str,
+# ]
+#
+# It doesn't seem possible to have recursive types, so just go with Any.
+Piece = t.Any # pylint: disable=invalid-name
+
+
+def _render_text_pieces(piece: Piece, indent_level: int,
+ into: t.List[str]) -> None:
+ """Helper for |render_text_pieces|. Accumulates strs into |into|."""
+ if isinstance(piece, LineBreak):
+ into.append('\n' + indent_level * ' ')
+ return
+
+ if isinstance(piece, str):
+ into.append(piece)
+ return
+
+ if isinstance(piece, Bold):
+ into.append('**')
+ _render_text_pieces(piece.inner, indent_level, into)
+ into.append('**')
+ return
+
+ if isinstance(piece, Link):
+ # Don't even try; it's ugly more often than not.
+ _render_text_pieces(piece.inner, indent_level, into)
+ return
+
+ if isinstance(piece, UnorderedList):
+ for p in piece.items:
+ _render_text_pieces([line_break, '- ', p], indent_level + 2, into)
+ return
+
+ if isinstance(piece, Switch):
+ _render_text_pieces(piece.text, indent_level, into)
+ return
+
+ if isinstance(piece, (list, tuple)):
+ for p in piece:
+ _render_text_pieces(p, indent_level, into)
+ return
+
+ raise ValueError('Unknown piece type: %s' % type(piece))
+
+
+def render_text_pieces(piece: Piece) -> str:
+ """Renders the given Pieces into text."""
+ into = []
+ _render_text_pieces(piece, 0, into)
+ return ''.join(into)
+
+
+def _render_html_pieces(piece: Piece, into: t.List[str]) -> None:
+ """Helper for |render_html_pieces|. Accumulates strs into |into|."""
+ if piece is line_break:
+ into.append('<br />\n')
+ return
+
+ if isinstance(piece, str):
+ into.append(html.escape(piece))
+ return
+
+ if isinstance(piece, Bold):
+ into.append('<b>')
+ _render_html_pieces(piece.inner, into)
+ into.append('</b>')
+ return
+
+ if isinstance(piece, Link):
+ into.append('<a href="' + piece.href + '">')
+ _render_html_pieces(piece.inner, into)
+ into.append('</a>')
+ return
+
+ if isinstance(piece, UnorderedList):
+ into.append('<ul>\n')
+ for p in piece.items:
+ into.append('<li>')
+ _render_html_pieces(p, into)
+ into.append('</li>\n')
+ into.append('</ul>\n')
+ return
+
+ if isinstance(piece, Switch):
+ _render_html_pieces(piece.html, into)
+ return
+
+ if isinstance(piece, (list, tuple)):
+ for p in piece:
+ _render_html_pieces(p, into)
+ return
+
+ raise ValueError('Unknown piece type: %s' % type(piece))
+
+
+def render_html_pieces(piece: Piece) -> str:
+ """Renders the given Pieces into HTML."""
+ into = []
+ _render_html_pieces(piece, into)
+ return ''.join(into)