CSV to Multi-Markdown Tables

One person noticed yesterday’s yak-shaving and liked it, which was all the excuse I needed to shave it some more. But I am good to go for now, though this still is still fairly fragile: you have to select only the table lines, there’s no way to escape a comma if you want it in a particular entry, it won’t catch if there are different number of items on each line, etc.

Here is the downloadable workflow .

I cleaned up the code in ways that were useful learning exercises for me and added support for >, <, = as separator indicators

so this:

one, two and a half, three and a quarter
four, five, six

still outputs to this:

|  one | two and a half | three and a quarter |
|  :---: |  :---: |  :---: |  
|  four | five | six | 

but now this:

one, two and a half, three and a quarter
>, =, <
four, five, six

outputs this:

|  one | two and a half | three and a quarter |
| ----: | :---: | :---- | 
|  four | five | six | 

Swift 3.0 code below (still vaguely embarrassed to share it):

#!/usr/bin/swift
import Foundation

let separators: [Character: String] = ["=": ":---:", ">": "----:", "<": ":----"]
let separatorSet = CharacterSet(charactersIn: "><=, ")

func titleAndEntriesFrom(string: String) -> (String, String?, [String])? {
    var lines = string.components(separatedBy: "\n")
    guard lines.count > 1, let titleLine = lines.first else {
        print("need more than one line ->")
        return nil
    }

    lines.removeFirst()
    let separatorMarkdown: String?
    if let second = lines.first where CharacterSet(charactersIn: second).isSubset(of: separatorSet) {
        separatorMarkdown = separatorFrom(line: second)
        lines.removeFirst()
    } else {
        separatorMarkdown = nil
    }
    return (titleLine, separatorMarkdown, lines)
}

func mdTableFrom(components: (String, String?, [String])) -> String? {
    let titleMarkdown = markdownFrom(line: components.0)
    let titleEntries = components.0.components(separatedBy: ",")
    let separatorMarkdown = components.1 ?? titleEntries.reduce("\n| "){prev, sep in "\(prev) :---: | "}
    let bodyMarkdown = components.2.reduce(""){prev, line in "\(prev) \(markdownFrom(line: line))"}

    return titleMarkdown + separatorMarkdown + bodyMarkdown
}

func separatorFrom(line: String) -> String {
    let entries = line.components(separatedBy: ",")
    let things = entries.flatMap({$0.trimmingCharacters(in: CharacterSet.whitespaces).characters.first})
    let markdown = things.reduce("\n|"){prev, entry in "\(prev) \(separators[entry] ?? ":xxx:") |"}
    return markdown
}

func markdownFrom(line: String) -> String {
    guard line.characters.count > 0 else {
        return "\n"
    }
    let entries = line.components(separatedBy: ",")
    return entries.reduce("\n| "){prev, entry in "\(prev) \(entry.trimmingCharacters(in: CharacterSet.whitespaces)) |"}
}

var string = ""

while let thing = readLine(strippingNewline: false) {
    string += thing
}

if let titleAndBody = titleAndEntriesFrom(string: string), output = mdTableFrom(components: titleAndBody) {
    print(output)
} else {
    print(string)
}
 
7
Kudos
 
7
Kudos

Now read this

Partial vs. Complete Functions

Whoo boy, stick with this seemingly complicated article (linked in the title), because it directly addresses that subtle issue that I face regularly when functions or methods that I write can’t properly map directly all input to your... Continue →