Task: Display an outline as a nested table




Go: package main

import (
    "fmt"
    "strings"
)

type nNode struct {
    name     string
    children []nNode
}

type iNode struct {
    level int
    name  string
}

func toNest(iNodes []iNode, start, level int, n *nNode) {
    if level == 0 {
        n.name = iNodes[0].name
    }
    for i := start + 1; i < len(iNodes); i++ {
        if iNodes[i].level == level+1 {
            c := nNode{iNodes[i].name, nil}
            toNest(iNodes, i, level+1, &c)
            n.children = append(n.children, c)
        } else if iNodes[i].level <= level {
            return
        }
    }
}

func makeIndent(outline string, tab int) []iNode {
    lines := strings.Split(outline, "\n")
    iNodes := make([]iNode, len(lines))
    for i, line := range lines {
        line2 := strings.TrimLeft(line, " ")
        le, le2 := len(line), len(line2)
        level := (le - le2) / tab
        iNodes[i] = iNode{level, line2}
    }
    return iNodes
}

func toMarkup(n nNode, cols []string, depth int) string {
    var span int

    var colSpan func(nn nNode)
    colSpan = func(nn nNode) {
        for i, c := range nn.children {
            if i > 0 {
                span++
            }
            colSpan(c)
        }
    }

    for _, c := range n.children {
        span = 1
        colSpan(c)
    }
    var lines []string
    lines = append(lines, `{| class="wikitable" style="text-align: center;"`)
    const l1, l2 = "|-", "|  |"
    lines = append(lines, l1)
    span = 1
    colSpan(n)
    s := fmt.Sprintf(`| style="background: %s " colSpan=%d | %s`, cols[0], span, n.name)
    lines = append(lines, s, l1)

    var nestedFor func(nn nNode, level, maxLevel, col int)
    nestedFor = func(nn nNode, level, maxLevel, col int) {
        if level == 1 && maxLevel > level {
            for i, c := range nn.children {
                nestedFor(c, 2, maxLevel, i)
            }
        } else if level < maxLevel {
            for _, c := range nn.children {
                nestedFor(c, level+1, maxLevel, col)
            }
        } else {
            if len(nn.children) > 0 {
                for i, c := range nn.children {
                    span = 1
                    colSpan(c)
                    cn := col + 1
                    if maxLevel == 1 {
                        cn = i + 1
                    }
                    s := fmt.Sprintf(`| style="background: %s " colspan=%d | %s`, cols[cn], span, c.name)
                    lines = append(lines, s)
                }
            } else {
                lines = append(lines, l2)
            }
        }
    }
    for maxLevel := 1; maxLevel < depth; maxLevel++ {
        nestedFor(n, 1, maxLevel, 0)
        if maxLevel < depth-1 {
            lines = append(lines, l1)
        }
    }
    lines = append(lines, "|}")
    return strings.Join(lines, "\n")
}

func main() {
    const outline = `Display an outline as a nested table.
    Parse the outline to a tree,
        measuring the indent of each line,
        translating the indentation to a nested structure,
        and padding the tree to even depth.
    count the leaves descending from each node,
        defining the width of a leaf as 1,
        and the width of a parent node as a sum.
            (The sum of the widths of its children) 
    and write out a table with 'colspan' values
        either as a wiki table,
        or as HTML.`
    const (
        yellow = "#ffffe6;"
        orange = "#ffebd2;"
        green  = "#f0fff0;"
        blue   = "#e6ffff;"
        pink   = "#ffeeff;"
    )
    cols := []string{yellow, orange, green, blue, pink}
    iNodes := makeIndent(outline, 4)
    var n nNode
    toNest(iNodes, 0, 0, &n)
    fmt.Println(toMarkup(n, cols, 4))

    fmt.Println("\n")
    const outline2 = `Display an outline as a nested table.
    Parse the outline to a tree,
        measuring the indent of each line,
        translating the indentation to a nested structure,
        and padding the tree to even depth.
    count the leaves descending from each node,
        defining the width of a leaf as 1,
        and the width of a parent node as a sum.
            (The sum of the widths of its children)
            Propagating the sums upward as necessary. 
    and write out a table with 'colspan' values
        either as a wiki table,
        or as HTML.
    Optionally add color to the nodes.`
    cols2 := []string{blue, yellow, orange, green, pink}
    var n2 nNode
    iNodes2 := makeIndent(outline2, 4)
    toNest(iNodes2, 0, 0, &n2)
    fmt.Println(toMarkup(n2, cols2, 4))
}




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)