xref: /aosp_15_r20/external/toolchain-utils/cros_utils/tiny_render.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li# Copyright 2020 The ChromiumOS Authors
2*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be
3*760c253cSXin Li# found in the LICENSE file.
4*760c253cSXin Li
5*760c253cSXin Li"""A super minimal module that allows rendering of readable text/html.
6*760c253cSXin Li
7*760c253cSXin LiUsage should be relatively straightforward. You wrap things you want to write
8*760c253cSXin Liout in some of the nice types defined here, and then pass the result to one of
9*760c253cSXin Lirender_text_pieces/render_html_pieces.
10*760c253cSXin Li
11*760c253cSXin LiIn HTML, the types should all nest nicely. In text, eh (nesting anything in
12*760c253cSXin LiBold is going to be pretty ugly, probably).
13*760c253cSXin Li
14*760c253cSXin LiLists and tuples may be used to group different renderable elements.
15*760c253cSXin Li
16*760c253cSXin LiExample:
17*760c253cSXin Li
18*760c253cSXin Lirender_text_pieces([
19*760c253cSXin Li  Bold("Daily to-do list:"),
20*760c253cSXin Li  UnorderedList([
21*760c253cSXin Li    "Write code",
22*760c253cSXin Li    "Go get lunch",
23*760c253cSXin Li    ["Fix ", Bold("some"), " of the bugs in the aforementioned code"],
24*760c253cSXin Li    [
25*760c253cSXin Li      "Do one of the following:",
26*760c253cSXin Li      UnorderedList([
27*760c253cSXin Li        "Nap",
28*760c253cSXin Li        "Round 2 of lunch",
29*760c253cSXin Li        ["Look at ", Link("https://google.com/?q=memes", "memes")],
30*760c253cSXin Li      ]),
31*760c253cSXin Li    ],
32*760c253cSXin Li    "What a rough day; time to go home",
33*760c253cSXin Li  ]),
34*760c253cSXin Li])
35*760c253cSXin Li
36*760c253cSXin LiTurns into
37*760c253cSXin Li
38*760c253cSXin Li**Daily to-do list:**
39*760c253cSXin Li  - Write code
40*760c253cSXin Li  - Go get lunch
41*760c253cSXin Li  - Fix **some** of the bugs in said code
42*760c253cSXin Li  - Do one of the following:
43*760c253cSXin Li    - Nap
44*760c253cSXin Li    - Round 2 of lunch
45*760c253cSXin Li    - Look at memes
46*760c253cSXin Li  - What a rough day; time to go home
47*760c253cSXin Li
48*760c253cSXin Li...And similarly in HTML, though with an actual link.
49*760c253cSXin Li
50*760c253cSXin LiThe rendering functions should never mutate your input.
51*760c253cSXin Li"""
52*760c253cSXin Li
53*760c253cSXin Li
54*760c253cSXin Liimport collections
55*760c253cSXin Liimport html
56*760c253cSXin Liimport typing as t
57*760c253cSXin Li
58*760c253cSXin Li
59*760c253cSXin LiBold = collections.namedtuple("Bold", ["inner"])
60*760c253cSXin LiLineBreak = collections.namedtuple("LineBreak", [])
61*760c253cSXin LiLink = collections.namedtuple("Link", ["href", "inner"])
62*760c253cSXin LiUnorderedList = collections.namedtuple("UnorderedList", ["items"])
63*760c253cSXin Li# Outputs different data depending on whether we're emitting text or HTML.
64*760c253cSXin LiSwitch = collections.namedtuple("Switch", ["text", "html"])
65*760c253cSXin Li
66*760c253cSXin Liline_break = LineBreak()
67*760c253cSXin Li
68*760c253cSXin Li# Note that these build up their values in a funky way: they append to a list
69*760c253cSXin Li# that ends up being fed to `''.join(into)`. This avoids quadratic string
70*760c253cSXin Li# concatenation behavior. Probably doesn't matter, but I care.
71*760c253cSXin Li
72*760c253cSXin Li# Pieces are really a recursive type:
73*760c253cSXin Li# Union[
74*760c253cSXin Li#   Bold,
75*760c253cSXin Li#   LineBreak,
76*760c253cSXin Li#   Link,
77*760c253cSXin Li#   List[Piece],
78*760c253cSXin Li#   Tuple[...Piece],
79*760c253cSXin Li#   UnorderedList,
80*760c253cSXin Li#   str,
81*760c253cSXin Li# ]
82*760c253cSXin Li#
83*760c253cSXin Li# It doesn't seem possible to have recursive types, so just go with Any.
84*760c253cSXin LiPiece = t.Any  # pylint: disable=invalid-name
85*760c253cSXin Li
86*760c253cSXin Li
87*760c253cSXin Lidef _render_text_pieces(
88*760c253cSXin Li    piece: Piece, indent_level: int, into: t.List[str]
89*760c253cSXin Li) -> None:
90*760c253cSXin Li    """Helper for |render_text_pieces|. Accumulates strs into |into|."""
91*760c253cSXin Li    if isinstance(piece, LineBreak):
92*760c253cSXin Li        into.append("\n" + indent_level * " ")
93*760c253cSXin Li        return
94*760c253cSXin Li
95*760c253cSXin Li    if isinstance(piece, str):
96*760c253cSXin Li        into.append(piece)
97*760c253cSXin Li        return
98*760c253cSXin Li
99*760c253cSXin Li    if isinstance(piece, Bold):
100*760c253cSXin Li        into.append("**")
101*760c253cSXin Li        _render_text_pieces(piece.inner, indent_level, into)
102*760c253cSXin Li        into.append("**")
103*760c253cSXin Li        return
104*760c253cSXin Li
105*760c253cSXin Li    if isinstance(piece, Link):
106*760c253cSXin Li        # Don't even try; it's ugly more often than not.
107*760c253cSXin Li        _render_text_pieces(piece.inner, indent_level, into)
108*760c253cSXin Li        return
109*760c253cSXin Li
110*760c253cSXin Li    if isinstance(piece, UnorderedList):
111*760c253cSXin Li        for p in piece.items:
112*760c253cSXin Li            _render_text_pieces([line_break, "- ", p], indent_level + 2, into)
113*760c253cSXin Li        return
114*760c253cSXin Li
115*760c253cSXin Li    if isinstance(piece, Switch):
116*760c253cSXin Li        _render_text_pieces(piece.text, indent_level, into)
117*760c253cSXin Li        return
118*760c253cSXin Li
119*760c253cSXin Li    if isinstance(piece, (list, tuple)):
120*760c253cSXin Li        for p in piece:
121*760c253cSXin Li            _render_text_pieces(p, indent_level, into)
122*760c253cSXin Li        return
123*760c253cSXin Li
124*760c253cSXin Li    raise ValueError("Unknown piece type: %s" % type(piece))
125*760c253cSXin Li
126*760c253cSXin Li
127*760c253cSXin Lidef render_text_pieces(piece: Piece) -> str:
128*760c253cSXin Li    """Renders the given Pieces into text."""
129*760c253cSXin Li    into: t.List[str] = []
130*760c253cSXin Li    _render_text_pieces(piece, 0, into)
131*760c253cSXin Li    return "".join(into)
132*760c253cSXin Li
133*760c253cSXin Li
134*760c253cSXin Lidef _render_html_pieces(piece: Piece, into: t.List[str]) -> None:
135*760c253cSXin Li    """Helper for |render_html_pieces|. Accumulates strs into |into|."""
136*760c253cSXin Li    if piece is line_break:
137*760c253cSXin Li        into.append("<br />\n")
138*760c253cSXin Li        return
139*760c253cSXin Li
140*760c253cSXin Li    if isinstance(piece, str):
141*760c253cSXin Li        into.append(html.escape(piece))
142*760c253cSXin Li        return
143*760c253cSXin Li
144*760c253cSXin Li    if isinstance(piece, Bold):
145*760c253cSXin Li        into.append("<b>")
146*760c253cSXin Li        _render_html_pieces(piece.inner, into)
147*760c253cSXin Li        into.append("</b>")
148*760c253cSXin Li        return
149*760c253cSXin Li
150*760c253cSXin Li    if isinstance(piece, Link):
151*760c253cSXin Li        into.append('<a href="' + piece.href + '">')
152*760c253cSXin Li        _render_html_pieces(piece.inner, into)
153*760c253cSXin Li        into.append("</a>")
154*760c253cSXin Li        return
155*760c253cSXin Li
156*760c253cSXin Li    if isinstance(piece, UnorderedList):
157*760c253cSXin Li        into.append("<ul>\n")
158*760c253cSXin Li        for p in piece.items:
159*760c253cSXin Li            into.append("<li>")
160*760c253cSXin Li            _render_html_pieces(p, into)
161*760c253cSXin Li            into.append("</li>\n")
162*760c253cSXin Li        into.append("</ul>\n")
163*760c253cSXin Li        return
164*760c253cSXin Li
165*760c253cSXin Li    if isinstance(piece, Switch):
166*760c253cSXin Li        _render_html_pieces(piece.html, into)
167*760c253cSXin Li        return
168*760c253cSXin Li
169*760c253cSXin Li    if isinstance(piece, (list, tuple)):
170*760c253cSXin Li        for p in piece:
171*760c253cSXin Li            _render_html_pieces(p, into)
172*760c253cSXin Li        return
173*760c253cSXin Li
174*760c253cSXin Li    raise ValueError("Unknown piece type: %s" % type(piece))
175*760c253cSXin Li
176*760c253cSXin Li
177*760c253cSXin Lidef render_html_pieces(piece: Piece) -> str:
178*760c253cSXin Li    """Renders the given Pieces into HTML."""
179*760c253cSXin Li    into: t.List[str] = []
180*760c253cSXin Li    _render_html_pieces(piece, into)
181*760c253cSXin Li    return "".join(into)
182