diff --git a/pytools/__init__.py b/pytools/__init__.py
index 1b1608b0dc445141a03807a913ec2230367823ee..9f8113ebb163411a236b8070306aba14317e0325 100644
--- a/pytools/__init__.py
+++ b/pytools/__init__.py
@@ -1553,26 +1553,30 @@ a_star = MovedFunctionDeprecationWrapper(a_star_moved)
 class Table:
     """An ASCII table generator.
 
-    .. attribute:: nrows
-    .. attribute:: ncolumns
-
-    .. attribute:: alignments
-
-        A :class:`tuple` of alignments of each column: ``"l"``, ``"c"``, or ``"r"``,
-        for left, center, and right alignment, respectively). Columns which
-        have no alignment specifier will use the last specified alignment. For
-        example, with ``alignments=("l", "r")``, the third and all following
-        columns will use right alignment.
-
+    .. automethod:: __init__
     .. automethod:: add_row
 
+    .. autoproperty:: nrows
+    .. autoproperty:: ncolumns
+
     .. automethod:: __str__
     .. automethod:: github_markdown
     .. automethod:: csv
     .. automethod:: latex
+    .. automethod:: text_without_markup
     """
 
     def __init__(self, alignments: Optional[Tuple[str, ...]] = None) -> None:
+        """Create a new :class:`Table`.
+
+        :arg alignments: A :class:`tuple` of alignments of each column:
+            ``"l"``, ``"c"``, or ``"r"``, for left, center, and right
+            alignment, respectively). Columns which have no alignment specifier
+            will use the last specified alignment. For example, with
+            ``alignments=("l", "r")``, the third and all following
+            columns will use right alignment.
+        """
+
         if alignments is None:
             alignments = ("l",)
         else:
@@ -1586,13 +1590,17 @@ class Table:
 
     @property
     def nrows(self) -> int:
+        """The number of rows currently in the table."""
         return len(self.rows)
 
     @property
     def ncolumns(self) -> int:
+        """The number of columns currently in the table."""
         return len(self.rows[0])
 
     def add_row(self, row: Tuple[Any, ...]) -> None:
+        """Add *row* to the table. Note that all rows must have the same number
+        of columns."""
         if self.rows and len(row) != self.ncolumns:
             raise ValueError(
                     f"tried to add a row with {len(row)} columns to "
@@ -1756,6 +1764,38 @@ class Table:
 
         return "\n".join(lines)
 
+    def text_without_markup(self) -> str:
+        """Returns a string representation of the table without markup.
+
+        .. doctest::
+
+            >>> tbl = Table()
+            >>> tbl.add_row([0, "orange"])
+            >>> tbl.add_row([1111, "apple"])
+            >>> tbl.add_row([2, "pear"])
+            >>> print(tbl.text_without_markup())
+            0    orange
+            1111 apple
+            2    pear
+        """
+        if not self.rows:
+            return ""
+
+        alignments = self._get_alignments()
+        col_widths = self._get_column_widths(self.rows)
+
+        lines = [" ".join([
+            cell.center(col_width) if align == "c"
+            else cell.ljust(col_width) if align == "l"
+            else cell.rjust(col_width)
+            for cell, col_width, align in zip(row, col_widths, alignments)])
+            for row in self.rows]
+
+        # Remove the extra space added by the last cell
+        lines = [line.rstrip() for line in lines]
+
+        return "\n".join(lines)
+
 
 def merge_tables(*tables: Table,
         skip_columns: Optional[Tuple[int, ...]] = None) -> Table: