~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
69 lines · 2.7 KB Raw History
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) }
    }
}