johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
import Foundation
/// A table in Markdown, as GitHub's flavour has them: a line of cells, a line of
/// dashes under it with one column to each, then a line of cells to each row.
struct MarkdownTable: Hashable {
enum Alignment: Hashable { case leading, center, trailing }
let header: [String]
let alignments: [Alignment]
private(set) var rows: [[String]] = []
/// None when `dashes` isn't such a line, or doesn't match `header`'s columns,
/// which leaves them both as words.
init?(header line: String, dashes: String) {
guard line.contains("|"), dashes.contains("|") else { return nil }
let columns = Self.cells(dashes)
guard columns.allSatisfy(Self.isDashes) else { return nil }
header = Self.cells(line)
guard header.count == columns.count else { return nil }
alignments = columns.map { column in
switch (column.hasPrefix(":"), column.hasSuffix(":")) {
case (true, true): .center
case (false, true): .trailing
default: .leading
}
}
}
/// Dashes, with a colon at either end or both.
private static func isDashes(_ column: String) -> Bool {
var dashes = Substring(column)
if dashes.hasPrefix(":") { dashes.removeFirst() }
if dashes.hasSuffix(":") { dashes.removeLast() }
return !dashes.isEmpty && dashes.allSatisfy { $0 == "-" }
}
/// A row has the table's columns, whatever its line had: those it lacks are
/// empty, and those it has beyond them go.
mutating func add(_ line: String) {
let cells = Self.cells(line).prefix(header.count)
rows.append(cells + Array(repeating: "", count: header.count - cells.count))
}
/// What's between a line's pipes, but for one with a backslash before it,
/// which is a pipe in the cell. Those at either end hold nothing.
static func cells(_ line: String) -> [String] {
var cells: [String] = []
var cell = ""
var escaped = false
for character in line {
if escaped {
cell += character == "|" ? "|" : "\\\(character)"
escaped = false
} else if character == "\\" {
escaped = true
} else if character == "|" {
cells.append(cell)
cell = ""
} else {
cell.append(character)
}
}
cells.append(escaped ? cell + "\\" : cell)
let trimmed = line.trimmingCharacters(in: .whitespaces)
if trimmed.hasPrefix("|") { cells.removeFirst() }
if trimmed.hasSuffix("|"), !trimmed.hasSuffix("\\|"), !cells.isEmpty { cells.removeLast() }
return cells.map { $0.trimmingCharacters(in: .whitespaces) }
}
}