llvmbot monitor: Convert table output to a builder class

We could import a library to do this but it doesn't seem
worth it just for tables.

This uses a content manager so you don't have to write the
begin and end of each cell/row/table. Each method returns
self so you can just keep calling methods to build what you
need.

Change-Id: I8eef664f41039a9fbc662a148f292efd61866694
diff --git a/monitor/make_table.py b/monitor/make_table.py
new file mode 100644
index 0000000..2f115b5
--- /dev/null
+++ b/monitor/make_table.py
@@ -0,0 +1,132 @@
+# This file contains a basic "builder" style API for making HTML tables
+# and writing them to a file once finished.
+#
+# Use it as follows:
+# with Table(outfile) as table:
+#  table.AddRow().AddCell("foo")
+#
+# To get:
+# <table>
+#   <td>foo</td>
+# </table>
+#
+# Methods return a reference to self, or to the new thing you added.
+# This means you can keep chaining calls to build what you want.
+#
+# table.AddRow().AddCell("foo").Colspan(1).Style("mystyle")
+
+
+class TableCell(object):
+    def __init__(self, name, content=None):
+        self.name = name
+        self.content = content
+        self.style = None
+        self.colspan = None
+
+    def Style(self, style):
+        self.style = style
+        return self
+
+    def Colspan(self, colspan):
+        self.colspan = colspan
+        return self
+
+    def Content(self, content):
+        self.content = content
+        return self
+
+    def __str__(self):
+        return "    <{}{}{}>{}</{}>".format(
+            self.name,
+            "" if self.style is None else ' style="{}"'.format(self.style),
+            "" if self.colspan is None else " colspan={}".format(self.colspan),
+            "&nbsp;" if self.content is None else self.content,
+            self.name,
+        )
+
+
+class Cell(TableCell):
+    def __init__(self, content=None):
+        super(Cell, self).__init__("td", content)
+
+
+class Header(TableCell):
+    def __init__(self, content=None):
+        super(Header, self).__init__("th", content)
+
+
+class Row(object):
+    def __init__(self):
+        self.cells = []
+
+    def AddCell(self, content=None):
+        self.cells.append(Cell(content))
+        return self.cells[-1]
+
+    def AddHeader(self, content=None):
+        self.cells.append(Header(content))
+        return self.cells[-1]
+
+    def __str__(self):
+        return "\n".join(["  <tr>", *map(str, self.cells), "  </tr>"])
+
+
+class TableBody(object):
+    def __init__(self, close=False):
+        self.close = close
+
+    def __str__(self):
+        return "<{}tbody>".format("/" if self.close else "")
+
+
+class Table(object):
+    def __init__(self, out):
+        self.out = out
+        self.rows = []
+        self.border = None
+        self.cellspacing = None
+        self.cellpadding = None
+        self.body_begins = None
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *args):
+        self.out.write("\n" + str(self))
+
+    def __str__(self):
+        open_tag = "<table{}{}{}>".format(
+            "" if self.border is None else " border={}".format(self.border),
+            ""
+            if self.cellspacing is None
+            else " cellspacing={}".format(self.cellspacing),
+            ""
+            if self.cellpadding is None
+            else " cellpadding={}".format(self.cellpadding),
+        )
+        rows = map(str, self.rows)
+        close_tag = "</table>"
+
+        return "\n".join([open_tag, *rows, close_tag])
+
+    def AddRow(self):
+        self.rows.append(Row())
+        return self.rows[-1]
+
+    def Border(self, border):
+        self.border = border
+        return self
+
+    def Cellspacing(self, cellspacing):
+        self.cellspacing = cellspacing
+        return self
+
+    def Cellpadding(self, cellpadding):
+        self.cellpadding = cellpadding
+        return self
+
+    def BeginBody(self):
+        self.rows.append(TableBody())
+
+    def EndBody(self):
+        self.rows.append(TableBody(close=True))