Skip to content

Commit

Permalink
[pkl.table] Add HTML style (#67)
Browse files Browse the repository at this point in the history
Adds an HTML style to the Table renderer.
  • Loading branch information
HT154 authored Jun 28, 2024
1 parent 0e868f4 commit 3528213
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 28 deletions.
4 changes: 2 additions & 2 deletions packages/pkl.table/PklProject
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
amends "../basePklProject.pkl"

package {
version = "1.0.1"
description = "Generates pretty human-readable and markdown-compatible tables."
version = "1.1.0"
description = "Generates pretty human-readable, markdown-compatible, and HTML tables."
}
117 changes: 91 additions & 26 deletions packages/pkl.table/table.pkl
Original file line number Diff line number Diff line change
Expand Up @@ -39,40 +39,55 @@ class Column {
align: Alignment = "left"
}

/// Used to provide fully custom content for an entire table row
/// One possible usage is to express colspan in HTML tables
open class RowDirective {
content: String

function render(style: TableStyle): String = content
}

/// Style options used to draw the table
/// Default values produce a table consisting of simple ASCII characters
class TableStyle {
open class TableStyle {
/// Placeholder to use in place of null property values
nullPlaceholder: String = "null"

/// Include a header in the table
includeHeader: Boolean = true

/// Default character to use for horizontal rules
defaultHorizontal: Char = "-"
defaultHorizontal: String(!fixedWidth || length == 1) = "-"

/// Indicates whether or not the table is expected to produce fixed-width output
/// Disabling this enables styles to produce markup languages like HTML
fixedWidth = true

/// Characters to use for specific horizontal rules
horizontals: Mapping<VerticalPosition, Char?> = new {
horizontals: Mapping<VerticalPosition, String(!fixedWidth || length == 1)?> = new {
["top"] = defaultHorizontal
["inner"] = defaultHorizontal
["bottom"] = defaultHorizontal
}

/// Default character to use for vertical rules
defaultVertical: Char = "|"
defaultVertical: String(!fixedWidth || length == 1) = "|"

/// Characters to use for specific horizontal rules
verticals: Mapping<HorizontalPosition, Char?> = new {
verticals: Mapping<HorizontalPosition, String(!fixedWidth || length == 1)?> = new {
["left"] = defaultVertical
["inner"] = defaultVertical
["right"] = defaultVertical
}

/// Characters to use for specific vertical rules in the header
headerVerticals: Mapping<HorizontalPosition, String(!fixedWidth || length == 1)?> = verticals

/// Default character to use for corners
defaultCorner: Char = "+"
defaultCorner: String(!fixedWidth || length == 1) = "+"

/// Characters to use for specific corners
corners: Mapping<HorizontalPosition, Mapping<VerticalPosition, Char>> = new {
corners: Mapping<HorizontalPosition, Mapping<VerticalPosition, String(!fixedWidth || length == 1)>> = new {
["left"] {
["top"] = defaultCorner
["inner"] = defaultCorner
Expand All @@ -91,6 +106,36 @@ class TableStyle {
}
}

/// Style options specific to HTML tables
/// Default values produce a table with two-space indentation
open class TableStyleHTML extends TableStyle {
/// Controls which character(s) are used for one indentation level
indent: String = " "

/// Indent controls the baseline indent level applied to every line
baseIndent: String = ""

local _style = this

fixedWidth = false
defaultCorner = ""
horizontals {
["top"] = "\(baseIndent)<table>\n\(baseIndent)\(indent)<\(if (_style.includeHeader) "thead" else "tbody")>"
["inner"] = "\(baseIndent)\(indent)</thead>\n\(baseIndent)\(indent)<tbody>"
["bottom"] = "\(baseIndent)\(indent)</tbody>\n\(baseIndent)</table>"
}
verticals {
["left"] = "\(baseIndent)\(indent.repeat(2))<tr>\n\(baseIndent)\(indent.repeat(3))<td>"
["inner"] = "</td>\n\(baseIndent)\(indent.repeat(3))<td>"
["right"] = "</td>\n\(baseIndent)\(indent.repeat(2))</tr>"
}
headerVerticals {
["left"] = "\(baseIndent)\(indent.repeat(2))<tr>\n\(baseIndent)\(indent.repeat(3))<th>"
["inner"] = "</th>\n\(baseIndent)\(indent.repeat(3))<th>"
["right"] = "</th>\n\(baseIndent)\(indent.repeat(2))</tr>"
}
}

local class InterimTable {

style: TableStyle
Expand All @@ -110,11 +155,15 @@ local class InterimTable {
}
}

renderedCells: Listing<Mapping<ColumnKey, String>> = new {
renderedCells: Listing<*Mapping<ColumnKey, String> | RowDirective> = new {
for (row in rows) {
new {
for (column in columns) {
[column.key] = renderCell(column.key, row.getPropertyOrNull(column.key))
when (row is RowDirective) {
row
} else {
new {
for (column in columns) {
[column.key] = renderCell(column.key, row.getPropertyOrNull(column.key))
}
}
}
}
Expand All @@ -125,13 +174,17 @@ local class InterimTable {
renderHorizontalRule("top")
}
when (style.includeHeader) {
renderRow(columns.toList().toMap((col) -> col.key, (col) -> col.title))
renderRow(columns.toList().toMap((col) -> col.key, (col) -> col.title), true)
when (style.horizontals["inner"] != null) {
renderHorizontalRule("inner")
}
}
for (row in renderedCells) {
renderRow(row.toMap())
when (row is RowDirective) {
row.render(style)
} else {
renderRow(row.toMap(), false)
}
}
when (style.horizontals["bottom"] != null) {
renderHorizontalRule("bottom")
Expand All @@ -144,25 +197,34 @@ local class InterimTable {
value?.toString() ??
style.nullPlaceholder

function renderRow(renderedCells: Map<ColumnKey, String>) = new Listing {
style.verticals["left"] ?? ""
function renderRow(renderedCells: Map<ColumnKey, String>, inHeader: Boolean) = new Listing {
local verticals = if (inHeader) style.headerVerticals else style.verticals
verticals["left"] ?? ""
for (i, col in columns) {
" "
if (col.align == "left")
renderedCells[col.key].padEnd(columnWidths[col.key], " ")
else if (col.align == "right")
renderedCells[col.key].padStart(columnWidths[col.key], " ")
else ""
" "
style.verticals[if (i == columns.length - 1) "right" else "inner"] ?? ""
when (style.fixedWidth) {
" "
if (col.align == "left")
renderedCells[col.key].padEnd(columnWidths[col.key], " ")
else if (col.align == "right")
renderedCells[col.key].padStart(columnWidths[col.key], " ")
else ""
" "
} else {
renderedCells[col.key]
}
verticals[if (i == columns.length - 1) "right" else "inner"] ?? ""
}
}.join("")

function renderHorizontalRule(verticalPosition: VerticalPosition): String = new Listing {
style.corners["left"][verticalPosition]
for (i, col in columns) {
style.horizontals[verticalPosition].repeat(columnWidths[col.key] + 2)
style.corners[if (i == columns.length - 1) "right" else "inner"][verticalPosition]
when (style.fixedWidth) {
for (i, col in columns) {
style.horizontals[verticalPosition].repeat(columnWidths[col.key] + 2)
style.corners[if (i == columns.length - 1) "right" else "inner"][verticalPosition]
}
} else {
style.horizontals[verticalPosition]
}
}.join("")
}
Expand Down Expand Up @@ -249,3 +311,6 @@ const markdownStyle: TableStyle = new {
["bottom"] = null
}
}

/// [TableStyle] that renders HTML tables
const htmlStyle: TableStyleHTML = new {}
24 changes: 24 additions & 0 deletions packages/pkl.table/tests/table.pkl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ examples {
}
}.renderDocument(data)
}
["row directive"] {
new table.TableRenderer {
style = table.htmlStyle
columns { table.Column("alpha"); table.Column("beta"); table.Column("gamma") }
}.renderDocument((data) {
new table.RowDirective { content = "\(table.htmlStyle.indent.repeat(2))<tr><th colspan=\"3\">this is a colspan</th></tr>" }
})
}
["coverter"] {
new table.TableRenderer {
columns { table.Column("alpha"); table.Column("beta"); table.Column("gamma"); table.Column("delta") }
Expand Down Expand Up @@ -78,4 +86,20 @@ examples {
columns { table.Column("alpha"); table.Column("beta"); table.Column("gamma") }
}.renderDocument(data)
}
["html"] {
new table.TableRenderer {
style = table.htmlStyle
columns { table.Column("alpha"); table.Column("beta"); table.Column("gamma") }
}.renderDocument(data)
}
["html with custom indent and no header"] {
new table.TableRenderer {
style = (table.htmlStyle) {
baseIndent = "indent:"
indent = "\t"
includeHeader = false
}
columns { table.Column("alpha"); table.Column("beta"); table.Column("gamma") }
}.renderDocument(data)
}
}
87 changes: 87 additions & 0 deletions packages/pkl.table/tests/table.pkl-expected.pcf
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,38 @@ examples {

"""
}
["row directive"] {
"""
<table>
<thead>
<tr>
<th>alpha</th>
<th>beta</th>
<th>gamma</th>
</tr>
</thead>
<tbody>
<tr>
<td>123</td>
<td>123</td>
<td>123.h</td>
</tr>
<tr>
<td>456</td>
<td>456</td>
<td>456.h</td>
</tr>
<tr>
<td>789loooooong</td>
<td>789</td>
<td>789.h</td>
</tr>
<tr><th colspan="3">this is a colspan</th></tr>
</tbody>
</table>

"""
}
["coverter"] {
"""
+--------------+------+-----------+-------+
Expand Down Expand Up @@ -67,6 +99,61 @@ examples {
| 456 | 456 | 456.h |
| 789loooooong | 789 | 789.h |

"""
}
["html"] {
"""
<table>
<thead>
<tr>
<th>alpha</th>
<th>beta</th>
<th>gamma</th>
</tr>
</thead>
<tbody>
<tr>
<td>123</td>
<td>123</td>
<td>123.h</td>
</tr>
<tr>
<td>456</td>
<td>456</td>
<td>456.h</td>
</tr>
<tr>
<td>789loooooong</td>
<td>789</td>
<td>789.h</td>
</tr>
</tbody>
</table>

"""
}
["html with custom indent and no header"] {
"""
indent:<table>
indent:\t<tbody>
indent:\t\t<tr>
indent:\t\t\t<td>123</td>
indent:\t\t\t<td>123</td>
indent:\t\t\t<td>123.h</td>
indent:\t\t</tr>
indent:\t\t<tr>
indent:\t\t\t<td>456</td>
indent:\t\t\t<td>456</td>
indent:\t\t\t<td>456.h</td>
indent:\t\t</tr>
indent:\t\t<tr>
indent:\t\t\t<td>789loooooong</td>
indent:\t\t\t<td>789</td>
indent:\t\t\t<td>789.h</td>
indent:\t\t</tr>
indent:\t</tbody>
indent:</table>

"""
}
}

0 comments on commit 3528213

Please sign in to comment.