Task: Display an outline as a nested table







Python: 

import itertools
import re
import sys

from collections import deque
from typing import NamedTuple


RE_OUTLINE = re.compile(r"^((?: |\t)*)(.+)$", re.M)

COLORS = itertools.cycle(
    [
        "
        "
        "
        "
        "
    ]
)


class Node:
    def __init__(self, indent, value, parent, children=None):
        self.indent = indent
        self.value = value
        self.parent = parent
        self.children = children or []

        self.color = None

    def depth(self):
        if self.parent:
            return self.parent.depth() + 1
        return -1

    def height(self):
        
        if not self.children:
            return 0
        return max(child.height() for child in self.children) + 1

    def colspan(self):
        if self.leaf:
            return 1
        return sum(child.colspan() for child in self.children)

    @property
    def leaf(self):
        return not bool(self.children)

    def __iter__(self):
        
        q = deque()
        q.append(self)
        while q:
            node = q.popleft()
            yield node
            q.extend(node.children)


class Token(NamedTuple):
    indent: int
    value: str


def tokenize(outline):
    
    for match in RE_OUTLINE.finditer(outline):
        indent, value = match.groups()
        yield Token(len(indent), value)


def parse(outline):
    
    
    tokens = list(tokenize(outline))

    
    temp_root = Node(-1, "", None)
    _parse(tokens, 0, temp_root)

    
    root = temp_root.children[0]
    pad_tree(root, root.height())

    return root


def _parse(tokens, index, node):
    
    
    if index >= len(tokens):
        return

    token = tokens[index]

    if token.indent == node.indent:
        
        current = Node(token.indent, token.value, node.parent)
        node.parent.children.append(current)
        _parse(tokens, index + 1, current)

    elif token.indent > node.indent:
        
        current = Node(token.indent, token.value, node)
        node.children.append(current)
        _parse(tokens, index + 1, current)

    elif token.indent < node.indent:
        
        _parse(tokens, index, node.parent)


def pad_tree(node, height):
    
    if node.leaf and node.depth() < height:
        pad_node = Node(node.indent + 1, "", node)
        node.children.append(pad_node)

    for child in node.children:
        pad_tree(child, height)


def color_tree(node):
    
    if not node.value:
        node.color = "
    elif node.depth() <= 1:
        node.color = next(COLORS)
    else:
        node.color = node.parent.color

    for child in node.children:
        color_tree(child)


def table_data(node):
    
    indent = "    "

    if node.colspan() > 1:
        colspan = f'colspan="{node.colspan()}"'
    else:
        colspan = ""

    if node.color:
        style = f'style="background-color: {node.color};"'
    else:
        style = ""

    attrs = " ".join([colspan, style])
    return f"{indent}<td{attrs}>{node.value}</td>"


def html_table(tree):
    
    
    table_cols = tree.colspan()

    
    row_cols = 0

    
    buf = ["<table style='text-align: center;'>"]

    
    for node in tree:
        if row_cols == 0:
            buf.append("  <tr>")

        buf.append(table_data(node))
        row_cols += node.colspan()

        if row_cols == table_cols:
            buf.append("  </tr>")
            row_cols = 0

    buf.append("</table>")
    return "\n".join(buf)


def wiki_table_data(node):
    
    if not node.value:
        return "|  |"

    if node.colspan() > 1:
        colspan = f"colspan={node.colspan()}"
    else:
        colspan = ""

    if node.color:
        style = f'style="background: {node.color};"'
    else:
        style = ""

    attrs = " ".join([colspan, style])
    return f"| {attrs} | {node.value}"


def wiki_table(tree):
    
    
    table_cols = tree.colspan()

    
    row_cols = 0

    
    buf = ['{| class="wikitable" style="text-align: center;"']

    for node in tree:
        if row_cols == 0:
            buf.append("|-")

        buf.append(wiki_table_data(node))
        row_cols += node.colspan()

        if row_cols == table_cols:
            row_cols = 0

    buf.append("|}")
    return "\n".join(buf)


def example(table_format="wiki"):
    

    outline = (
        "Display an outline as a nested table.\n"
        "    Parse the outline to a tree,\n"
        "        measuring the indent of each line,\n"
        "        translating the indentation to a nested structure,\n"
        "        and padding the tree to even depth.\n"
        "    count the leaves descending from each node,\n"
        "        defining the width of a leaf as 1,\n"
        "        and the width of a parent node as a sum.\n"
        "            (The sum of the widths of its children)\n"
        "    and write out a table with 'colspan' values\n"
        "        either as a wiki table,\n"
        "        or as HTML."
    )

    tree = parse(outline)
    color_tree(tree)

    if table_format == "wiki":
        print(wiki_table(tree))
    else:
        print(html_table(tree))


if __name__ == "__main__":
    args = sys.argv[1:]

    if len(args) == 1:
        table_format = args[0]
    else:
        table_format = "wiki"

    example(table_format)