はじめに
特に実用性はない。深夜テンションで思いついて気が付いた頃には実行ボタン押してた。
実行結果

使い方
せっかくなので使ってみてね
あとで下に書くファイル一つ分をコピペするだけで使えるよ💕
AsciiTable.swift
実行コードもとっても簡単!
Sample.swift
// サンプル用のデータ
let sample = Ascii.Table.Sample.jsonData
// 初期化
let table = Ascii.Table(json: sample)
// コンソール 出力
for text in table.texts {
print(text)
}
// JSONに変換して取得
let json = table.jsonData
コピペ用ファイル
丸々コピーして適当なファイルに貼り付けてね😊
Ascii.Tableクラスが本体だよ🤗
String
の配列とか、JSON
とか色んな形式から初期化できるから試してみよう!
AsciiTable.swift
//
// +------------------+
// | AsciiTable.swift |
// +------------------+
//
import Foundation
/*
+------------+
| How to Use |
+------------+
``
let sample = Ascii.Table.Sample.jsonData
# 1. init
let table = Ascii.Table(json: sample)
# 2. print
for text in table.texts {
print(text)
}
# 3. Serialize
let json = table.jsonData
``
*/
extension Ascii.Table {
public enum Sample {
/// this is a sample
public static let rawTexts: (columns: [String], rows: [[String]]) = {
return (
columns: ["1", "2", "3"],
rows: [
["4", "5", "6"],
["7", "8", "9"]
]
)
}()
/// this is a sample
public static let jsonData: Data = """
{
"columns": [
{"key": "keyA", "width": 8},
{"key": "keyB", "width": 10}
],
"rows": [
{"cells":[
{"key": "keyA", "value": "valueA0"},
{"key": "keyB", "value": "valueB0"}
]},
{"cells": [
{"key": "keyA", "value": "valueA1"},
{"key": "keyB", "value": "valueB1"}
]}
]
}
""".data(using: .utf8)!
/// this is a sample
public static let rows: [Row] = [
Row(cells: [
Cell(key: "XXX",
value: "aaa"),
Cell(key: "YYY",
value: "bbb")
]),
Row(cells: [
Cell(key: "XXX",
value: "ccc"),
Cell(key: "YYY",
value: "ddd")
]),
]
}
}
fileprivate protocol AsciiTable {
/// [
/// "+----------+------------+",
/// "| keyA | keyB |",
/// "+----------+------------+",
/// "| valueA0 | valueB0 |",
/// "+----------+------------+",
/// "| valueA1 | valueB1 |",
/// "+----------+------------+"
/// ]
/// texts for output
var texts: [String] { get }
/// init with Swift Type
init(columns: [Ascii.Table.Column]?, rows: [Ascii.Table.Row])
/// init with String Array
init(columns: [String], rows: [[String]])
}
public enum Ascii {
public struct Table {
public struct Column {
let key: String
var width: Int?
}
public struct Row {
let cells: [Cell]
}
public struct Cell {
let key: String
let value: String
}
public let columns: [Column]
public let rows: [Row]
}
}
extension Ascii.Table: AsciiTable {
init(columns: [Ascii.Table.Column]? = nil, rows: [Ascii.Table.Row]) {
self.columns = (columns ?? rows.first?.cells.map { Column(key: $0.key) }) ?? []
self.rows = rows
}
init(columns: [String], rows: [[String]]) {
self.columns = columns.map {Column(key: $0)}
self.rows = rows.map {(row) -> Row in Row(cells: row.enumerated().map {Cell(key: columns[$0.offset], value: $0.element)})}
}
var texts: [String] {
let borderLines = [String](repeating: self.horizontalBorder, count: self.rows.count).map { [$0] }
let rowLines = self.rows.map {self.stringRow(for: $0.cells)}
let zipped = zip(borderLines, rowLines)
let alts = [String](zipped.map { $0.0 + $0.1 }.joined())
return [self.horizontalBorder]
+ [self.stringColumns]
+ alts
+ [self.horizontalBorder]
}
}
extension Ascii.Table: JSONSerializable {}
extension Ascii.Table.Row: JSONSerializable {}
extension Ascii.Table.Column: JSONSerializable {}
extension Ascii.Table.Cell: JSONSerializable {}
fileprivate extension Ascii.Table.Cell {
func lineBrokenValue(by count: Int) -> [String] {
return self.value.split(by: count).map {$0.filled(to: count)}
}
}
fileprivate extension Ascii.Table {
func cells(at line: Int) -> [Ascii.Table.Cell]? {
return self.rows[at: line]?.cells
}
var horizontalBorder: String {
return "+-" + self.columns.map {[String](repeating: "-", count: $0.width ?? $0.key.count).joined()}.joined(separator: "-+-") + "-+"
}
var stringColumns: String {
return "| " + self.columns.map {$0.key.filled(to: $0.width ?? $0.key.count)}.joined(separator: " | ") + " |"
}
func stringRow(for cells: [Ascii.Table.Cell]) -> [String] {
let lineBrokens = self.columns.map { c -> [String] in
return cells.first(where: {$0.key == c.key})!.lineBrokenValue(by: c.width ?? c.key.count)
}
let cellHeight = lineBrokens.max(by: {return $0.count < $1.count})!.count
let filledToHeight = lineBrokens.map {$0 + [String](repeating: "", count: cellHeight - $0.count)}
return filledToHeight.transpose().map {"| " + $0.joined(separator: " | ") + " |"}
}
}
protocol JSONSerializable: Codable {
init(json: Data)
var jsonData: Data { get }
}
extension JSONSerializable {
init(json: Data) {
self = try! JSONDecoder().decode(Self.self, from: json)
}
var jsonData: Data {
return try! JSONEncoder().encode(self)
}
}
fileprivate extension String {
func split(by count: Int) -> [String] {
let arrayValue = Array(self)
if arrayValue.isEmpty { return [] }
var parent: [[Element]] = []
for i in 1 ... arrayValue.count {
if (i % count == 0) {
parent.append(arrayValue[(i - count)...(i - 1)].map{$0})
}
}
if arrayValue.count % count != 0 { parent.append(arrayValue.suffix(arrayValue.count % count)) }
return parent.map({String($0)})
}
func filled(to count: Int, with pad: String = " ", isleftJustified: Bool = false) -> String {
let main = String(self.prefix(count))
let pads = String(repeating: pad, count: count - main.count)
return isleftJustified ? (main + pads) : (pads + main)
}
}
fileprivate extension Array {
subscript (at index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
extension Array where Element: RandomAccessCollection, Element.Index == Int {
func transpose() -> [[Element.Element]] {
return self.isEmpty ? [] : (0...(self.first!.endIndex - 1)).map { i -> [Element.Element] in self.map { $0[i] } }
}
}
さいごに
実用性はないと書きましたが、実は色んなコーディング要素が詰まっているので何かの題材に使えそうかも?!
せっかくだから他の機能も増やしてコマンドラインツールでも作ってみようかしら。 ちょうどテーブル表示なのでDB初期化用の init.sql
と .json
ファイルの相互変換とかが良いでしょうか。
...
... ...
はぁ、平日の夜中に何やってんだ俺。。。もう無理、マヂ寝ョ