TRAVIS_OS_NAME=osx <<<<<< ENV codecov.yml .github/CODEOWNERS LICENSE Makefile Package.swift Sources/MarkdownGenerator/Extensions.swift Sources/MarkdownGenerator/MarkdownBlockquotes.swift Sources/MarkdownGenerator/MarkdownCodeBlock.swift Sources/MarkdownGenerator/MarkdownCollapsibleSection.swift Sources/MarkdownGenerator/MarkdownConvertible.swift Sources/MarkdownGenerator/MarkdownFile.swift Sources/MarkdownGenerator/MarkdownHeader.swift Sources/MarkdownGenerator/MarkdownImage.swift Sources/MarkdownGenerator/MarkdownLink.swift Sources/MarkdownGenerator/MarkdownList.swift Sources/MarkdownGenerator/MarkdownTable.swift Tests/LinuxMain.swift Tests/MarkdownGeneratorTests/Internal/MarkdownTableInternalTests.swift Tests/MarkdownGeneratorTests/PublicInterface/ConsecutiveBlankLinesTests.swift Tests/MarkdownGeneratorTests/PublicInterface/ExtensionsTests.swift Tests/MarkdownGeneratorTests/PublicInterface/MarkdownBlockquotesTests.swift Tests/MarkdownGeneratorTests/PublicInterface/MarkdownCodeBlockTests.swift Tests/MarkdownGeneratorTests/PublicInterface/MarkdownCollapsibleSectionTests.swift Tests/MarkdownGeneratorTests/PublicInterface/MarkdownConvertibleTests.swift Tests/MarkdownGeneratorTests/PublicInterface/MarkdownFileTests.swift Tests/MarkdownGeneratorTests/PublicInterface/MarkdownHeaderTests.swift Tests/MarkdownGeneratorTests/PublicInterface/MarkdownImageTests.swift Tests/MarkdownGeneratorTests/PublicInterface/MarkdownLinkTests.swift Tests/MarkdownGeneratorTests/PublicInterface/MarkdownListTests.swift Tests/MarkdownGeneratorTests/PublicInterface/MarkdownTableTests.swift <<<<<< network # path=MarkdownGenerator.framework.coverage.txt /Users/travis/build/eneko/MarkdownGenerator/Sources/MarkdownGenerator/Extensions.swift: 1| |// 2| |// Extensions.swift 3| |// MarkdownGenerator 4| |// 5| |// Created by Eneko Alonso on 10/8/17. 6| |// 7| | 8| |import Foundation 9| | 10| |// MARK: - String: MarkdownConvertible 11| | 12| |extension String: MarkdownConvertible { 13| | 14| | /// Render a string of text as Markdown. No transformations applied. 15| 84| public var markdown: String { 16| 84| return self 17| 84| } 18| | 19| | /// Remove consecutive blank lines from a string output 20| 12| public var removingConsecutiveBlankLines: String { 21| 12| let lines = components(separatedBy: .newlines) 22| 44| let filtered: [(offset: Int, element: String)] = lines.enumerated().filter { line in 23| 44| return line.offset == 0 || 24| 44| lines[line.offset].trimmingCharacters(in: .whitespaces).isEmpty == false || 25| 44| lines[line.offset - 1].trimmingCharacters(in: .whitespaces).isEmpty == false 26| 44| } 27| 35| return filtered.map { $0.element }.joined(separator: String.newLine) 28| 12| } 29| | 30| | static let newLine = "\n" 31| |} 32| | 33| |extension Array: MarkdownConvertible { 34| | /// Render a collection of Markdown convertible elements. 35| | /// 36| | /// Elements are rendered separated by one blank line, to prevent formatting interference. 37| 4| public var markdown: String { 38| 12| return compactMap { ($0 as? MarkdownConvertible)?.markdown }.joined(separator: "\n\n") 39| 4| } 40| |} /Users/travis/build/eneko/MarkdownGenerator/Sources/MarkdownGenerator/MarkdownBlockquotes.swift: 1| |// 2| |// MarkdownBlockquotes.swift 3| |// MarkdownGenerator 4| |// 5| |// Created by Eneko Alonso on 10/8/17. 6| |// 7| | 8| |import Foundation 9| | 10| |/// Render Markdown Blockquotes 11| |/// 12| |/// Markdown uses email-style > characters for blockquoting. 13| |/// 14| |/// > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, 15| |/// > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. 16| |/// > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. 17| |/// > 18| |/// > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse 19| |/// > id sem consectetuer libero luctus adipiscing. 20| |/// 21| |public struct MarkdownBlockquotes: MarkdownConvertible { 22| | 23| | let content: MarkdownConvertible 24| | 25| | /// MarkdownBlockquotes initializer 26| | /// 27| | /// - Parameter content: Content to be block-quoted with '> ' 28| 1| public init(content: MarkdownConvertible) { 29| 1| self.content = content 30| 1| } 31| | 32| | /// Generated Markdown output 33| 1| public var markdown: String { 34| 1| return content.blockquoted.markdown 35| 1| } 36| |} 37| | 38| |// MARK: - MarkdownBlockquote 39| |extension MarkdownConvertible { 40| | /// Quoted version of the generated Markdown output of the current entity. 41| | /// 42| | /// "## H2 Header".blockquoted // > ## H2 Header 43| | /// 44| 10| public var blockquoted: MarkdownConvertible { 45| 10| let input = markdown 46| 10| if input.isEmpty { 47| 1| return "" 48| 9| } 49| 9| let lines = markdown.components(separatedBy: String.newLine) 50| 75| let quoted: [String] = lines.map { "> \($0)".trimmingCharacters(in: .whitespaces) } 51| 9| return quoted.joined(separator: String.newLine) 52| 10| } 53| |} /Users/travis/build/eneko/MarkdownGenerator/Sources/MarkdownGenerator/MarkdownCodeBlock.swift: 1| |// 2| |// MarkdownCodeBlock.swift 3| |// MarkdownGenerator 4| |// 5| |// Created by Eneko Alonso on 10/18/17. 6| |// 7| | 8| |import Foundation 9| | 10| |public struct MarkdownCodeBlock: MarkdownConvertible { 11| | let code: String 12| | let style: CodeBlockStyle 13| | 14| 5| public init(code: String, style: CodeBlockStyle = .indented) { 15| 5| self.code = code 16| 5| self.style = style 17| 5| } 18| | 19| | /// Generated Markdown output 20| 5| public var markdown: String { 21| 5| switch style { 22| 5| case .indented: 23| 2| let lines = code.components(separatedBy: .newlines) 24| 7| let indented: [String] = lines.map { $0.isEmpty ? "" : " \($0)" } 25| 2| return indented.joined(separator: String.newLine) 26| 5| case .backticks(let language): 27| 3| return """ 28| 3| ```\(language) 29| 3| \(code) 30| 3| ``` 31| 5| """ 32| 5| } 33| 5| } 34| | 35| |} 36| | 37| |/// Markdown format for the generated code block 38| |/// 39| |/// - indented: Code block is indented by 4-spaces 40| |/// - backticks: Code block is wrapped with ``` 41| |public enum CodeBlockStyle { 42| | case indented 43| | case backticks(language: String) 44| |} /Users/travis/build/eneko/MarkdownGenerator/Sources/MarkdownGenerator/MarkdownCollapsibleSection.swift: 1| |// 2| |// MarkdownCollapsibleSection.swift 3| |// MarkdownGenerator 4| |// 5| |// Created by Eneko Alonso on 10/18/17. 6| |// 7| | 8| |import Foundation 9| | 10| |/// Collapsible blocks are a great way to hide large portions of content 11| |/// that, while valuable to the reader, would result on a lot of noise. 12| |/// 13| |/// Collapsible blocks work well in most Markdown readers, including 14| |/// GitHub.com and rendered GitHub Pages. However, because they rely on 15| |/// HTML tags, they will make the _raw_ markdown output harder to read. 16| |/// 17| |/// ```html 18| |///
Block Title 19| |/// 20| |/// Block details. 21| |/// 22| |///
23| |/// ``` 24| |public struct MarkdownCollapsibleSection: MarkdownConvertible { 25| | let summary: String 26| | let details: MarkdownConvertible 27| | 28| | /// MarkdownCollapsibleSection initializer 29| | /// 30| | /// - Parameters: 31| | /// - summary: Plain text or HTML string containing the block title. 32| | /// - details: Markdown convertible elements to include in the collapsible block. 33| 2| public init(summary: String, details: MarkdownConvertible) { 34| 2| self.summary = summary 35| 2| self.details = details 36| 2| } 37| | 38| | /// Generated Markdown output 39| 2| public var markdown: String { 40| 2| return """ 41| 2|
\(summary) 42| 2| 43| 2| \(details.markdown) 44| 2| 45| 2|
46| 2| """ 47| 2| } 48| | 49| |} /Users/travis/build/eneko/MarkdownGenerator/Sources/MarkdownGenerator/MarkdownFile.swift: 1| |// 2| |// MarkdownFile.swift 3| |// MarkdownGenerator 4| |// 5| |// Created by Eneko Alonso on 10/8/17. 6| |// 7| | 8| |import Foundation 9| | 10| |/// Helper structure to write Markdown files to disk. 11| |public struct MarkdownFile { 12| | 13| | /// Name of the Markdown file, without extension. 14| | public let filename: String 15| | 16| | /// Path where the Markdown file will be written to. 17| | /// 18| | /// Path can be absolute or relative to the working directory. It should 19| | /// not contain a trailing slash, nor the name of the file to write. 20| | /// 21| | /// Path will be created if it doesn't already exist in the system. 22| | public let basePath: String 23| | 24| | /// MarkdownConvertible entity that will be rendered 25| | /// as the Markdown content of the file. Can be an `Array`. 26| | public var content: MarkdownConvertible 27| | 28| | /// MarkdownFile initializer 29| | /// 30| | /// - Parameters: 31| | /// - filename: Name of the Markdown file, without extension. 32| | /// - basePath: Path where the Markdown file will be written to. 33| | /// 34| | /// Path can be absolute or relative to the working directory. It should 35| | /// not contain a trailing slash, nor the name of the file to write. 36| | /// 37| | /// Path will be created if it doesn't already exist in the system. 38| | /// 39| | /// - content: MarkdownConvertible entity that will be rendered 40| | /// as the Markdown content of the file. Can be an `Array`. 41| 3| public init(filename: String, basePath: String = "", content: MarkdownConvertible) { 42| 3| self.filename = filename.trimmingCharacters(in: .whitespacesAndNewlines) 43| 3| self.basePath = basePath.trimmingCharacters(in: .whitespacesAndNewlines) 44| 3| self.content = content 45| 3| } 46| | 47| | /// Computed property containing the file path (`/.md`) 48| 3| public var filePath: String { 49| 3| return basePath.isEmpty ? "\(filename).md" : "\(basePath)/\(filename).md" 50| 3| } 51| | 52| | /// Generate and write the Markdown file to disk. 53| | /// 54| | /// - Will override the file if already existing, or create a new one. 55| | /// - Will create the path directory structure if it does not exists. 56| | /// 57| | /// - Throws: Throws an exception if the file could not be written to disk, or 58| | /// if the path could not be created. 59| 2| public func write() throws { 60| 2| try createDirectory(path: basePath) 61| 2| let output = content.markdown.removingConsecutiveBlankLines 62| 2| try output.write(toFile: filePath, atomically: true, encoding: .utf8) 63| 2| } 64| | 65| 2| private func createDirectory(path: String) throws { 66| 2| guard path.isEmpty == false else { 67| 1| return 68| 1| } 69| 1| var isDir: ObjCBool = false 70| 1| if FileManager.default.fileExists(atPath: path, isDirectory: &isDir) == false { 71| 1| try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) 72| 1| } 73| 1| } 74| |} /Users/travis/build/eneko/MarkdownGenerator/Sources/MarkdownGenerator/MarkdownHeader.swift: 1| |// 2| |// MarkdownHeader.swift 3| |// MarkdownGenerator 4| |// 5| |// Created by Eneko Alonso on 10/8/17. 6| |// 7| | 8| |import Foundation 9| | 10| |/// Renders a Markdown Header 11| |/// 12| |/// Markdown supports two styles of headers, Setext and atx. 13| |/// 14| |/// Setext-style headers are β€œunderlined” using equal signs (for first-level headers) 15| |/// and dashes (for second-level headers). For example: 16| |/// 17| |/// This is an H1 18| |/// ============= 19| |/// 20| |/// This is an H2 21| |/// ------------- 22| |/// 23| |/// Atx-style headers use 1-6 hash characters at the start of the line, corresponding 24| |/// to HTML header levels 1-6. For example: 25| |/// 26| |/// # This is an H1 27| |/// 28| |/// ## This is an H2 29| |/// 30| |/// ###### This is an H6 31| |/// 32| |/// Atx-style headers can be closed (this is purely cosmetic): 33| |/// 34| |/// # This is an H1 # 35| |/// 36| |/// ## This is an H2 ## 37| |/// 38| |/// ### This is an H3 ### 39| |/// 40| |public struct MarkdownHeader: MarkdownConvertible { 41| | let title: String 42| | let level: MarkdownHeaderLevel 43| | let style: MarkdownHeaderStyle 44| | let close: Bool 45| | 46| | /// MarkdownHeader initializer. 47| | /// 48| | /// - Parameters: 49| | /// - title: Title of the header element 50| | /// - level: Header level (`h1`, `h2`... `h6`) 51| | /// - style: Header style: `setex` (underlined) or `atx` ('#') (defaults to `atx`). 52| | /// Setex format is only available for first-level (using equal signs) and 53| | /// second-level headers (using dashes). 54| | /// - close: Close `atx` style headers (defaults to `false`). When false, headers 55| | /// only include the '#' prefix. When `true`, headers also include the 56| | /// trailing '#' suffix: 57| | /// 58| | /// ### Third-level Header ### 59| | /// 60| | /// - SeeAlso: MarkdownHeaderLevel, MarkdownHeaderStyle 61| | public init(title: String, level: MarkdownHeaderLevel = .h1, style: MarkdownHeaderStyle = .atx, 62| 29| close: Bool = false) { 63| 29| self.title = title 64| 29| self.level = level 65| 29| self.style = level.rawValue < 3 ? style : .atx 66| 29| self.close = close 67| 29| } 68| | 69| | /// Generated Markdown output 70| 31| public var markdown: String { 71| 31| switch style { 72| 31| case .atx: 73| 27| let prefix = Array(repeating: "#", count: level.rawValue).joined() 74| 27| let suffix = close ? prefix : "" 75| 27| return "\(prefix) \(title) \(suffix)".trimmingCharacters(in: .whitespacesAndNewlines) 76| 31| case .setex: 77| 4| let symbol = level == .h1 ? "=" : "-" 78| 4| let underline = Array(repeating: symbol, count: title.count).joined() 79| 4| return """ 80| 4| \(title) 81| 4| \(underline) 82| 4| """ 83| 31| } 84| 31| } 85| 4|} 86| 4| 87| 4|/// Markdown Header level 88| 4|/// 89| 4|/// - h1: first-level headers 90| 4|/// 91| 4|/// # H1 Header 92| 4|/// 93| 4|/// - h2: second-level headers 94| 4|/// 95| 4|/// ## H2 Header 96| 4|/// 97| 4|/// - h3: third-level headers 98| 4|/// 99| 4|/// ### H3 Header 100| 4|/// 101| 4|/// - h4: fourth-level headers 102| 4|/// 103| 4|/// #### H4 Header 104| 4|/// 105| 4|/// - h5: fifth-level headers 106| 4|/// 107| 4|/// ##### H5 Header 108| 4|/// 109| 4|/// - h6: sixth-level headers 110| 4|/// 111| 4|/// ###### H6 Header 112| 4|/// 113| 4|public enum MarkdownHeaderLevel: Int { 114| 4| case h1 = 1 115| 4| case h2 116| 4| case h3 117| 4| case h4 118| 4| case h5 119| 4| case h6 120| 4|} 121| 4| 122| 4|/// Markdown Header render style 123| 4|/// 124| 4|/// - `setex`: Setext-style headers are β€œunderlined” using equal signs (for first-level headers) 125| 4|/// and dashes (for second-level headers). For example: 126| 4|/// 127| 4|/// This is a Setex H1 Header 128| 4|/// ========================= 129| 4|/// 130| 4|/// This is a Setex H2 Header 131| 4|/// ------------------------- 132| 4|/// 133| 4|/// - `atx`: Atx-style headers use 1-6 hash characters at the start of the line, corresponding 134| 4|/// to header levels 1-6. For example: 135| 4|/// 136| 4|/// # This is an Atx H1 Header 137| 4|/// 138| 4|/// ## This is an Atx H2 Header 139| 4|/// 140| 4|/// ###### This is a closed Atx H6 Header ###### 141| 4|/// 142| 4|public enum MarkdownHeaderStyle { 143| 4| case setex 144| 4| case atx 145| |} /Users/travis/build/eneko/MarkdownGenerator/Sources/MarkdownGenerator/MarkdownImage.swift: 1| |// 2| |// MarkdownImage.swift 3| |// MarkdownGenerator 4| |// 5| |// Created by Eneko Alonso on 10/9/17. 6| |// 7| | 8| |import Foundation 9| | 10| |/// Render an HTML image in Markdown format 11| |/// 12| |/// MarkdownImage(url: "http://example.com/image.jpg", altText: "SourceDocs Header").markdown 13| |/// 14| |/// Would render as: 15| |/// 16| |/// ![SourceDocs Header](http://example.com/image.jpg) 17| |/// 18| |public struct MarkdownImage: MarkdownConvertible { 19| | 20| | /// URL where the image is located. Can be absolute or relative. 21| | public let url: String 22| | 23| | /// Alternate text to display on non-graphic browsers or to be used by screen readers. 24| | public let altText: String 25| | 26| | /// MarkdownImage initializer 27| | /// 28| | /// - Parameters: 29| | /// - url: URL where the image is located. Can be absolute or relative. 30| | /// - altText: Alternate text to display on non-graphic browsers or to be used by screen readers. 31| 2| public init(url: String, altText: String = "") { 32| 2| self.url = url 33| 2| self.altText = altText 34| 2| } 35| | 36| | /// Generated Markdown output 37| 2| public var markdown: String { 38| 2| return "![\(altText)](\(url))" 39| 2| } 40| |} /Users/travis/build/eneko/MarkdownGenerator/Sources/MarkdownGenerator/MarkdownLink.swift: 1| |// 2| |// MarkdownLink.swift 3| |// MarkdownGenerator 4| |// 5| |// Created by Eneko Alonso on 10/9/17. 6| |// 7| | 8| |import Foundation 9| | 10| |/// Render an HTML link in Markdown format 11| |/// 12| |/// MarkdownLink(text: "Google", url: "https://example.com").markdown 13| |/// 14| |/// Would render as: 15| |/// 16| |/// [Google](https://example.com) 17| |/// 18| |public struct MarkdownLink: MarkdownConvertible { 19| | 20| | /// Text to display as hyper-linked. 21| | public let text: String 22| | 23| | /// Link URL, can be absolute, relative, or #local. 24| | public let url: String 25| | 26| | /// MarkdownLink initializer 27| | /// 28| | /// - Parameters: 29| | /// - text: Text to display as hyper-linked. 30| | /// - url: Link URL, can be absolute, relative, or #local. 31| 1| public init(text: String, url: String) { 32| 1| self.text = text 33| 1| self.url = url 34| 1| } 35| | 36| | /// Generated Markdown output 37| 1| public var markdown: String { 38| 1| return "[\(text)](\(url))" 39| 1| } 40| |} /Users/travis/build/eneko/MarkdownGenerator/Sources/MarkdownGenerator/MarkdownList.swift: 1| |// 2| |// MarkdownList.swift 3| |// MarkdownGenerator 4| |// 5| |// Created by Eneko Alonso on 10/9/17. 6| |// 7| | 8| |import Foundation 9| | 10| |/// Render a list of elements in Markdown format 11| |/// 12| |/// Unordered lists: 13| |/// 14| |/// - One item 15| |/// - Another item that expands to multiple 16| |/// lines. 17| |/// - A nested item. 18| |/// - Third-level. 19| |/// - Back to first-level. 20| |/// 21| |/// Ordered lists: 22| |/// 23| |/// 1. First item 24| |/// 1. Second item 25| |/// 1. Nested item (second-level) 26| |/// 1. Back to first-level 27| |/// 28| |public struct MarkdownList: MarkdownConvertible { 29| | let style: MarkdownListStyle 30| | 31| | /// List of items to be converted to a list. 32| | public var items: [MarkdownConvertible] 33| | 34| | /// MarkdownList initializer 35| | /// 36| | /// - Parameter items: List of items to be converted to a list. 37| 16| public init(items: [MarkdownConvertible], style: MarkdownListStyle = .unordered) { 38| 16| self.items = items 39| 16| self.style = style 40| 16| } 41| | 42| | /// Generated Markdown output 43| 16| public var markdown: String { 44| 55| return items.map { formatted(item: $0) }.joined(separator: String.newLine) 45| 16| } 46| | 47| | // Per Markdown documentation, indent all lines using 4 spaces. 48| 55| private func formatted(item: MarkdownConvertible) -> String { 49| 55| let symbol = style == .ordered ? "1. " : "- " 50| 55| var lines = item.markdown.components(separatedBy: String.newLine) 51| 55| let first = lines.removeFirst() 52| 55| let firstLine = item is MarkdownList ? " \(first)" : "\(symbol)\(first)" 53| 55| 54| 55| if lines.isEmpty { 55| 45| return firstLine 56| 45| } 57| 10| 58| 10| let blankLine = item is MarkdownList ? "" : String.newLine 59| 39| let indentedLines = lines.map { $0.isEmpty ? "" : " \($0)" }.joined(separator: String.newLine) + blankLine 60| 10| return """ 61| 10| \(firstLine) 62| 10| \(indentedLines) 63| 10| """ 64| 55| } 65| | 66| |} 67| | 68| |/// Specifies the type of list to be generated 69| |/// 70| |/// - ordered: Ordered lists use numbers followed by periods. 71| |/// - unordered: Unordered lists use hyphens as list markers. 72| |public enum MarkdownListStyle { 73| | case ordered 74| | case unordered 75| |} /Users/travis/build/eneko/MarkdownGenerator/Sources/MarkdownGenerator/MarkdownTable.swift: 1| |// 2| |// MarkdownTable.swift 3| |// MarkdownGenerator 4| |// 5| |// Created by Eneko Alonso on 10/9/17. 6| |// 7| | 8| |import Foundation 9| | 10| |/// Render a two dimensional Markdown table. 11| |/// 12| |/// | | Name | Department | 13| |/// | -- | ------ | ---------- | 14| |/// | 🍏 | Apple | Fruits | 15| |/// | 🍊 | Orange | Fruits | 16| |/// | πŸ₯– | Bread | Bakery | 17| |/// 18| |/// *Notes*: 19| |/// - Markdown tables are not supported by all Markdown readers. 20| |/// - Table headers are required. 21| |/// - Table cells cannot contain multiple lines. New line characters are replaced by a space. 22| |public struct MarkdownTable: MarkdownConvertible { 23| | 24| | let headers: [String] 25| | let data: [[String]] 26| | 27| | /// MarkdownTable initializer 28| | /// 29| | /// - Parameters: 30| | /// - headers: List of table header titles. 31| | /// - data: Two-dimensional `String` array with the table content. Rows are defined by 32| | /// the outer array, columns are defined by the inner arrays. 33| | /// 34| | /// An array of rows, each row containing an array of columns. All rows should contain the same 35| | /// number of columns as the headers array, to avoid formatting issues. 36| 8| public init(headers: [String], data: [[String]]) { 37| 9| self.headers = headers.map { $0.isEmpty ? " " : $0 } 38| 8| self.data = data 39| 8| } 40| | 41| | /// Generated Markdown output 42| 6| public var markdown: String { 43| 6| let columnWidths = computeColumnWidths() 44| 6| if columnWidths.isEmpty { 45| 1| return .newLine 46| 5| } 47| 5| let headerRow = makeRow(values: pad(values: headers, lengths: columnWidths)) 48| 10| let separatorRow = makeRow(values: columnWidths.map { String(repeating: "-", count: $0) }) 49| 12| let dataRows = data.map { columns in 50| 12| return makeRow(values: pad(values: columns, lengths: columnWidths)) 51| 12| } 52| 5| return """ 53| 5| \(headerRow) 54| 5| \(separatorRow) 55| 5| \(dataRows.joined(separator: String.newLine)) 56| 5| """ 57| 6| } 58| | 59| | /// Return max length for each column, counting individual UTF16 characters for better emoji support. 60| | /// 61| | /// - Returns: Array of column widths 62| 7| func computeColumnWidths() -> [Int] { 63| 7| let rows = [headers] + data 64| 19| guard let maxColumns = rows.map({ $0.count }).max(), maxColumns > 0 else { 65| 2| return [] 66| 5| } 67| 10| let columnWidths = (0.. Int in 68| 36| return columnLength(values: rows.compactMap({ $0.get(at: columnIndex) })) 69| 10| } 70| 5| return columnWidths 71| 7| } 72| | 73| 11| func columnLength(values: [String]) -> Int { 74| 31| return values.map({ $0.utf16.count }).max() ?? 0 75| 11| } 76| | 77| | /// Pad array of strings to a given length, counting individual UTF16 characters 78| | /// 79| | /// - Parameters: 80| | /// - values: array of strings to pad 81| | /// - lengths: desired lengths 82| | /// - Returns: array of right-padded strings 83| 17| func pad(values: [String], lengths: [Int]) -> [String] { 84| 17| var values = values 85| 22| while values.count < lengths.count { 86| 5| values.append("") 87| 17| } 88| 36| return zip(values, lengths).map { value, length in 89| 36| value + String(repeating: " ", count: max(0, length - value.utf16.count)) 90| 36| } 91| 17| } 92| | 93| | /// Convert a String array into a markdown formatter table row. 94| | /// Table cells cannot contain multiple lines. New line characters are replaced by a space. 95| | /// 96| | /// - Parameter values: array of values 97| | /// - Returns: Markdown formatted row 98| 22| func makeRow(values: [String]) -> String { 99| 46| let values = values.map { $0.replacingOccurrences(of: String.newLine, with: " ") } 100| 22| return "| " + values.joined(separator: " | ") + " |" 101| 22| } 102| |} 103| | 104| |extension Array { 105| 36| func get(at index: Int) -> Element? { 106| 36| guard (0.. This is a quote. 24| | """ 25| | 26| | let output = """ 27| | > ## This is a header. 28| | > 29| | > 1. This is the first list item. 30| | > 2. This is the second list item. 31| | > 32| | > Here's some example code: 33| | > 34| | > return shell_exec("echo $input | $markdown_script"); 35| | > 36| | > > This is a quote. 37| | """ 38| | 39| 1| func testBlockquotes() { 40| 1| XCTAssertEqual(MarkdownBlockquotes(content: input).markdown, output) 41| 1| } 42| | 43| 1| func testBlockquoted() { 44| 1| XCTAssertEqual(input.blockquoted.markdown, output) 45| 1| } 46| | 47| 1| func testNested() { 48| 1| XCTAssertEqual(input.blockquoted.blockquoted.markdown, output.blockquoted.markdown) 49| 1| } 50| | 51| 1| func testMultipleInputs() { 52| 1| XCTAssertEqual([input, input].blockquoted.markdown, [output, output].joined(separator: "\n>\n")) 53| 1| } 54| | 55| 1| func testSingleLine() { 56| 1| let input = """ 57| 1| Single line of text. 58| 1| """ 59| 1| XCTAssertEqual(input.blockquoted.markdown, "> Single line of text.") 60| 1| } 61| | 62| 1| func testWhitespaces() { 63| 1| XCTAssertEqual("".blockquoted.markdown, "") 64| 1| XCTAssertEqual("\n".blockquoted.markdown, ">\n>") 65| 1| XCTAssertEqual("\t".blockquoted.markdown, ">") 66| 1| } 67| | 68| | static var allTests = [ 69| | ("testBlockquotes", testBlockquotes), 70| | ("testBlockquoted", testBlockquoted), 71| | ("testNested", testNested), 72| | ("testMultipleInputs", testMultipleInputs), 73| | ("testSingleLine", testSingleLine), 74| | ("testWhitespaces", testWhitespaces) 75| | ] 76| | 77| |} /Users/travis/build/eneko/MarkdownGenerator/Tests/MarkdownGeneratorTests/PublicInterface/MarkdownCodeBlockTests.swift: 1| |// 2| |// MarkdownCodeBlockTests.swift 3| |// MarkdownGeneratorTests 4| |// 5| |// Created by Eneko Alonso on 10/18/17. 6| |// 7| | 8| |import XCTest 9| |import MarkdownGenerator 10| | 11| |class MarkdownCodeBlockTests: XCTestCase { 12| | 13| 1| func testEmpty() { 14| 1| let input = """ 15| 1| """ 16| 1| let output = """ 17| 1| """ 18| 1| XCTAssertEqual(MarkdownCodeBlock(code: input).markdown, output) 19| 1| } 20| | 21| 1| func testIndented() { 22| 1| let input = """ 23| 1| /// Defines an entity that can be represented as Markdown. 24| 1| public protocol MarkdownConvertible { 25| 1| 26| 1| /// Generated Markdown output representing the current entity. 27| 1| var markdown: String { get } 28| 1| } 29| 1| """ 30| 1| let output = """ 31| 1| /// Defines an entity that can be represented as Markdown. 32| 1| public protocol MarkdownConvertible { 33| 1| 34| 1| /// Generated Markdown output representing the current entity. 35| 1| var markdown: String { get } 36| 1| } 37| 1| """ 38| 1| XCTAssertEqual(MarkdownCodeBlock(code: input).markdown, output) 39| 1| } 40| | 41| 1| func testBackticks() { 42| 1| let input = """ 43| 1| /// Defines an entity that can be represented as Markdown. 44| 1| public protocol MarkdownConvertible { 45| 1| 46| 1| /// Generated Markdown output representing the current entity. 47| 1| var markdown: String { get } 48| 1| } 49| 1| """ 50| 1| let output = """ 51| 1| ``` 52| 1| /// Defines an entity that can be represented as Markdown. 53| 1| public protocol MarkdownConvertible { 54| 1| 55| 1| /// Generated Markdown output representing the current entity. 56| 1| var markdown: String { get } 57| 1| } 58| 1| ``` 59| 1| """ 60| 1| XCTAssertEqual(MarkdownCodeBlock(code: input, style: .backticks(language: "")).markdown, output) 61| 1| } 62| | 63| 1| func testBackticksSwift() { 64| 1| let input = """ 65| 1| /// Defines an entity that can be represented as Markdown. 66| 1| public protocol MarkdownConvertible { 67| 1| 68| 1| /// Generated Markdown output representing the current entity. 69| 1| var markdown: String { get } 70| 1| } 71| 1| """ 72| 1| let output = """ 73| 1| ```swift 74| 1| /// Defines an entity that can be represented as Markdown. 75| 1| public protocol MarkdownConvertible { 76| 1| 77| 1| /// Generated Markdown output representing the current entity. 78| 1| var markdown: String { get } 79| 1| } 80| 1| ``` 81| 1| """ 82| 1| XCTAssertEqual(MarkdownCodeBlock(code: input, style: .backticks(language: "swift")).markdown, output) 83| 1| } 84| | 85| |} /Users/travis/build/eneko/MarkdownGenerator/Tests/MarkdownGeneratorTests/PublicInterface/MarkdownCollapsibleSectionTests.swift: 1| |// 2| |// MarkdownCollapsibleSectionTests.swift 3| |// MarkdownGeneratorTests 4| |// 5| |// Created by Eneko Alonso on 10/18/17. 6| |// 7| | 8| |import XCTest 9| |import MarkdownGenerator 10| | 11| |class MarkdownCollapsibleSectionTests: XCTestCase { 12| | 13| 1| func testSimple() { 14| 1| let output = """ 15| 1|
Hello 16| 1| 17| 1| World 18| 1| 19| 1|
20| 1| """ 21| 1| XCTAssertEqual(MarkdownCollapsibleSection(summary: "Hello", details: "World").markdown, output) 22| 1| } 23| | 24| 1| func testComplex() { 25| 1| let output = """ 26| 1|
This is cool stuff 27| 1| 28| 1| # Title 29| 1| 30| 1| - 🐢 31| 1| - 🐱 32| 1| - 🦊 33| 1| 34| 1| | Name | Count | 35| 1| | ---- | ----- | 36| 1| | Dog | 1 | 37| 1| | Cat | 2 | 38| 1| 39| 1| ```swift 40| 1| let foo = Bar() 41| 1| ``` 42| 1| 43| 1|
44| 1| """ 45| 1| 46| 1| let details: [MarkdownConvertible] = [ 47| 1| MarkdownHeader(title: "Title"), 48| 1| MarkdownList(items: ["🐢", "🐱", "🦊"]), 49| 1| MarkdownTable(headers: ["Name", "Count"], data: [["Dog", "1"], ["Cat", "2"]]), 50| 1| MarkdownCodeBlock(code: "let foo = Bar()", style: .backticks(language: "swift")) 51| 1| ] 52| 1| XCTAssertEqual(MarkdownCollapsibleSection(summary: "This is cool stuff", details: details).markdown, output) 53| 1| } 54| | 55| |} /Users/travis/build/eneko/MarkdownGenerator/Tests/MarkdownGeneratorTests/PublicInterface/MarkdownConvertibleTests.swift: 1| |// 2| |// MarkdownConvertibleTests.swift 3| |// MarkdownGeneratorTests 4| |// 5| |// Created by Eneko Alonso on 10/9/17. 6| |// 7| | 8| |import XCTest 9| |import MarkdownGenerator 10| | 11| |class MarkdownConvertibleTests: XCTestCase { 12| | 13| 1| func testCustomStructureAdoptsProtocol() { 14| 1| XCTAssertEqual(Contact(firstName: "Bob", lastName: "Jones").markdown, "**Jones**, Bob") 15| 1| } 16| | 17| | static var allTests = [ 18| | ("testCustomStructureAdoptsProtocol", testCustomStructureAdoptsProtocol) 19| | ] 20| | 21| |} 22| | 23| |// Custom structure that can be rendered as Markdown 24| |struct Contact: MarkdownConvertible { 25| | let firstName: String 26| | let lastName: String 27| | 28| 1| var markdown: String { 29| 1| return "**\(lastName)**, \(firstName)" 30| 1| } 31| |} /Users/travis/build/eneko/MarkdownGenerator/Tests/MarkdownGeneratorTests/PublicInterface/MarkdownFileTests.swift: 1| |// 2| |// MarkdownFileTests.swift 3| |// MarkdownGeneratorTests 4| |// 5| |// Created by Eneko Alonso on 10/9/17. 6| |// 7| | 8| |import XCTest 9| |import MarkdownGenerator 10| | 11| |class MarkdownFileTests: XCTestCase { 12| | 13| 1| func testFilePath() { 14| 1| let file = MarkdownFile(filename: "Test", basePath: "Path/To/File", content: "") 15| 1| XCTAssertEqual(file.filePath, "Path/To/File/Test.md") 16| 1| } 17| | 18| 1| func testFileWrite() throws { 19| 1| let content = MarkdownHeader(title: "Hello World!") 20| 1| let file = MarkdownFile(filename: "WriteTest", content: content) 21| 1| try file.write() 22| 1| 23| 1| let fileContent = try String(contentsOfFile: "WriteTest.md") 24| 1| XCTAssertEqual(fileContent, content.markdown) 25| 1| } 26| | 27| 1| func testFileWriteWithFolder() throws { 28| 1| let content = MarkdownHeader(title: "Hello World!") 29| 1| let file = MarkdownFile(filename: "WriteTest", basePath: "AFolder", content: content) 30| 1| try file.write() 31| 1| 32| 1| let fileContent = try String(contentsOfFile: "AFolder/WriteTest.md") 33| 1| XCTAssertEqual(fileContent, content.markdown) 34| 1| } 35| | 36| | static var allTests = [ 37| | ("testFilePath", testFilePath) 38| | ] 39| | 40| |} /Users/travis/build/eneko/MarkdownGenerator/Tests/MarkdownGeneratorTests/PublicInterface/MarkdownHeaderTests.swift: 1| |// 2| |// BockElementsTests.swift 3| |// MarkdownGeneratorTests 4| |// 5| |// Created by Eneko Alonso on 10/8/17. 6| |// 7| | 8| |import XCTest 9| |import MarkdownGenerator 10| | 11| |class MarkdownHeaderTests: XCTestCase { 12| | 13| 1| func testDefaultHeader() { 14| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title").markdown, "# Header Title") 15| 1| } 16| | 17| 1| func testHeaderLevelsAtx() { 18| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h1).markdown, "# Header Title") 19| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h2).markdown, "## Header Title") 20| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h3).markdown, "### Header Title") 21| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h4).markdown, "#### Header Title") 22| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h5).markdown, "##### Header Title") 23| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h6).markdown, "###### Header Title") 24| 1| } 25| | 26| 1| func testHeaderLevelsSetex() { 27| 1| let h1 = """ 28| 1| Header Title 29| 1| ============ 30| 1| """ 31| 1| 32| 1| let h2 = """ 33| 1| Header Title 34| 1| ------------ 35| 1| """ 36| 1| 37| 1| let h3 = "### Header Title" 38| 1| let h4 = "#### Header Title" 39| 1| let h5 = "##### Header Title" 40| 1| let h6 = "###### Header Title" 41| 1| 42| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h1, style: .setex).markdown, h1) 43| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h2, style: .setex).markdown, h2) 44| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h3, style: .setex).markdown, h3) 45| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h4, style: .setex).markdown, h4) 46| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h5, style: .setex).markdown, h5) 47| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h6, style: .setex).markdown, h6) 48| 1| } 49| | 50| 1| func testHeaderLevelsAtxCosing() { 51| 1| let h1 = "# Header Title #" 52| 1| let h2 = "## Header Title ##" 53| 1| let h3 = "### Header Title ###" 54| 1| let h4 = "#### Header Title ####" 55| 1| let h5 = "##### Header Title #####" 56| 1| let h6 = "###### Header Title ######" 57| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h1, close: true).markdown, h1) 58| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h2, close: true).markdown, h2) 59| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h3, close: true).markdown, h3) 60| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h4, close: true).markdown, h4) 61| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h5, close: true).markdown, h5) 62| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h6, close: true).markdown, h6) 63| 1| } 64| | 65| 1| func testHeaderLevelsSetexClosing() { 66| 1| let h1 = """ 67| 1| Header Title 68| 1| ============ 69| 1| """ 70| 1| 71| 1| let h2 = """ 72| 1| Header Title 73| 1| ------------ 74| 1| """ 75| 1| 76| 1| let h3 = "### Header Title ###" 77| 1| let h4 = "#### Header Title ####" 78| 1| let h5 = "##### Header Title #####" 79| 1| let h6 = "###### Header Title ######" 80| 1| 81| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h1, style: .setex, close: true).markdown, h1) 82| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h2, style: .setex, close: true).markdown, h2) 83| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h3, style: .setex, close: true).markdown, h3) 84| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h4, style: .setex, close: true).markdown, h4) 85| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h5, style: .setex, close: true).markdown, h5) 86| 1| XCTAssertEqual(MarkdownHeader(title: "Header Title", level: .h6, style: .setex, close: true).markdown, h6) 87| 1| } 88| | 89| 1| func testCodeHeader() { 90| 1| XCTAssertEqual(MarkdownHeader(title: "`Header Title`").markdown, "# `Header Title`") 91| 1| } 92| | 93| | static var allTests = [ 94| | ("testDefaultHeader", testDefaultHeader), 95| | ("testHeaderLevelsAtx", testHeaderLevelsAtx), 96| | ("testHeaderLevelsSetex", testHeaderLevelsSetex), 97| | ("testHeaderLevelsAtxCosing", testHeaderLevelsAtxCosing), 98| | ("testHeaderLevelsSetexClosing", testHeaderLevelsSetexClosing), 99| | ("testCodeHeader", testCodeHeader) 100| | ] 101| | 102| |} /Users/travis/build/eneko/MarkdownGenerator/Tests/MarkdownGeneratorTests/PublicInterface/MarkdownImageTests.swift: 1| |// 2| |// MarkdownImageTests.swift 3| |// MarkdownGeneratorTests 4| |// 5| |// Created by Eneko Alonso on 10/9/17. 6| |// 7| | 8| |import XCTest 9| |import MarkdownGenerator 10| | 11| |class MarkdownImageTests: XCTestCase { 12| | 13| 1| func testImage() { 14| 1| XCTAssertEqual(MarkdownImage(url: "http://example.com/image.png").markdown, "![](http://example.com/image.png)") 15| 1| } 16| | 17| 1| func testImageAltText() { 18| 1| let image = MarkdownImage(url: "http://example.com/image.png", altText: "Alternate Text") 19| 1| XCTAssertEqual(image.markdown, "![Alternate Text](http://example.com/image.png)") 20| 1| } 21| | 22| | static var allTests = [ 23| | ("testImage", testImage), 24| | ("testImageAltText", testImageAltText) 25| | ] 26| | 27| |} /Users/travis/build/eneko/MarkdownGenerator/Tests/MarkdownGeneratorTests/PublicInterface/MarkdownLinkTests.swift: 1| |// 2| |// MarkdownLinkTests.swift 3| |// MarkdownGeneratorTests 4| |// 5| |// Created by Eneko Alonso on 10/9/17. 6| |// 7| | 8| |import XCTest 9| |import MarkdownGenerator 10| | 11| |class MarkdownLinkTests: XCTestCase { 12| | 13| 1| func testLink() { 14| 1| let link = MarkdownLink(text: "example.com", url: "http://example.com") 15| 1| XCTAssertEqual(link.markdown, "[example.com](http://example.com)") 16| 1| } 17| | 18| | static var allTests = [ 19| | ("testLink", testLink) 20| | ] 21| | 22| |} /Users/travis/build/eneko/MarkdownGenerator/Tests/MarkdownGeneratorTests/PublicInterface/MarkdownListTests.swift: 1| |// 2| |// MarkdownListTests.swift 3| |// MarkdownGeneratorTests 4| |// 5| |// Created by Eneko Alonso on 10/9/17. 6| |// 7| | 8| |import XCTest 9| |import MarkdownGenerator 10| | 11| |class MarkdownListTests: XCTestCase { 12| | 13| 1| func testUnorderedSimpleList() { 14| 1| let input = ["🍏", "🍌", "🍊", "πŸ‡"] 15| 1| 16| 1| let output = """ 17| 1| - 🍏 18| 1| - 🍌 19| 1| - 🍊 20| 1| - πŸ‡ 21| 1| """ 22| 1| 23| 1| XCTAssertEqual(MarkdownList(items: input).markdown, output) 24| 1| } 25| | 26| 1| func testOrderedSimpleList() { 27| 1| let input = ["🍏", "🍌", "🍊", "πŸ‡"] 28| 1| 29| 1| let output = """ 30| 1| 1. 🍏 31| 1| 1. 🍌 32| 1| 1. 🍊 33| 1| 1. πŸ‡ 34| 1| """ 35| 1| 36| 1| XCTAssertEqual(MarkdownList(items: input, style: .ordered).markdown, output) 37| 1| } 38| | 39| 1| func testUnorderedNestedList() { 40| 1| let input: [MarkdownConvertible] = [ 41| 1| "Fruits", 42| 1| MarkdownList(items: ["🍏", "🍌", "🍊", "πŸ‡"]), 43| 1| "Bakery", 44| 1| MarkdownList(items: ["πŸ₯–", "🍞", "🍰", "πŸŽ‚"]) 45| 1| ] 46| 1| 47| 1| let output = """ 48| 1| - Fruits 49| 1| - 🍏 50| 1| - 🍌 51| 1| - 🍊 52| 1| - πŸ‡ 53| 1| - Bakery 54| 1| - πŸ₯– 55| 1| - 🍞 56| 1| - 🍰 57| 1| - πŸŽ‚ 58| 1| """ 59| 1| 60| 1| XCTAssertEqual(MarkdownList(items: input).markdown, output) 61| 1| } 62| | 63| 1| func testOrderedNestedList() { 64| 1| let input: [MarkdownConvertible] = [ 65| 1| "Fruits", 66| 1| MarkdownList(items: ["🍏", "🍌", "🍊", "πŸ‡"], style: .ordered), 67| 1| "Bakery", 68| 1| MarkdownList(items: ["πŸ₯–", "🍞", "🍰", "πŸŽ‚"], style: .ordered) 69| 1| ] 70| 1| 71| 1| let output = """ 72| 1| 1. Fruits 73| 1| 1. 🍏 74| 1| 1. 🍌 75| 1| 1. 🍊 76| 1| 1. πŸ‡ 77| 1| 1. Bakery 78| 1| 1. πŸ₯– 79| 1| 1. 🍞 80| 1| 1. 🍰 81| 1| 1. πŸŽ‚ 82| 1| """ 83| 1| 84| 1| XCTAssertEqual(MarkdownList(items: input, style: .ordered).markdown, output) 85| 1| } 86| | 87| 1| func testMixedNestedList() { 88| 1| let input: [MarkdownConvertible] = [ 89| 1| "Fruits", 90| 1| MarkdownList(items: ["🍏", "🍌", "🍊", "πŸ‡"]), 91| 1| "Bakery", 92| 1| MarkdownList(items: ["πŸ₯–", "🍞", "🍰", "πŸŽ‚"]) 93| 1| ] 94| 1| 95| 1| let output = """ 96| 1| 1. Fruits 97| 1| - 🍏 98| 1| - 🍌 99| 1| - 🍊 100| 1| - πŸ‡ 101| 1| 1. Bakery 102| 1| - πŸ₯– 103| 1| - 🍞 104| 1| - 🍰 105| 1| - πŸŽ‚ 106| 1| """ 107| 1| 108| 1| XCTAssertEqual(MarkdownList(items: input, style: .ordered).markdown, output) 109| 1| } 110| | 111| 1| func testUnorderedThreeLevelList() { 112| 1| let citrics = MarkdownList(items: ["πŸ‹", "🍊"]) 113| 1| let list = MarkdownList(items: ["Fruits", MarkdownList(items: ["Citrics", citrics])]) 114| 1| 115| 1| let output = """ 116| 1| - Fruits 117| 1| - Citrics 118| 1| - πŸ‹ 119| 1| - 🍊 120| 1| """ 121| 1| 122| 1| XCTAssertEqual(list.markdown, output) 123| 1| } 124| | 125| 1| func testMultipleParagraphLists() { 126| 1| XCTAssertEqual(MarkdownList(items: multilineInput).markdown, multilineOutput) 127| 1| } 128| | 129| | static var allTests = [ 130| | ("testUnorderedSimpleList", testUnorderedSimpleList), 131| | ("testOrderedSimpleList", testOrderedSimpleList), 132| | ("testUnorderedNestedList", testUnorderedNestedList), 133| | ("testOrderedNestedList", testOrderedNestedList), 134| | ("testMixedNestedList", testMixedNestedList), 135| | ("testUnorderedThreeLevelList", testUnorderedThreeLevelList), 136| | ("testMultipleParagraphLists", testMultipleParagraphLists) 137| | ] 138| | 139| |} 140| | 141| |let multilineInput = [ 142| | """ 143| | Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec odio. 144| | Quisque volutpat mattis eros. Nullam malesuada erat ut turpis. 145| | Suspendisse urna nibh, viverra non, semper suscipit, posuere a, pede. 146| | 147| | Indented code block 148| | 149| | Donec nec justo eget felis facilisis fermentum. Aliquam porttitor mauris 150| | sit amet orci. Aenean dignissim pellentesque felis. 151| | 152| | Morbi in sem quis dui placerat ornare. Pellentesque odio nisi, euismod in, 153| | pharetra a, ultricies in, diam. Sed arcu. Cras consequat. 154| | """, 155| | 156| | """ 157| | Pellentesque fermentum dolor. Aliquam quam lectus, facilisis auctor, ultrices 158| | ut, elementum vulputate, nunc. 159| | 160| | > Blockquote paragraph that expands to 161| | > multiple lines 162| | 163| | Sed adipiscing ornare risus. Morbi est est, blandit sit amet, sagittis vel, 164| | euismod vel, velit. Pellentesque egestas sem. Suspendisse commodo 165| | ullamcorper magna. 166| | """ 167| |] 168| | 169| |let multilineOutput = """ 170| |- Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec odio. 171| | Quisque volutpat mattis eros. Nullam malesuada erat ut turpis. 172| | Suspendisse urna nibh, viverra non, semper suscipit, posuere a, pede. 173| | 174| | Indented code block 175| | 176| | Donec nec justo eget felis facilisis fermentum. Aliquam porttitor mauris 177| | sit amet orci. Aenean dignissim pellentesque felis. 178| | 179| | Morbi in sem quis dui placerat ornare. Pellentesque odio nisi, euismod in, 180| | pharetra a, ultricies in, diam. Sed arcu. Cras consequat. 181| | 182| |- Pellentesque fermentum dolor. Aliquam quam lectus, facilisis auctor, ultrices 183| | ut, elementum vulputate, nunc. 184| | 185| | > Blockquote paragraph that expands to 186| | > multiple lines 187| | 188| | Sed adipiscing ornare risus. Morbi est est, blandit sit amet, sagittis vel, 189| | euismod vel, velit. Pellentesque egestas sem. Suspendisse commodo 190| | ullamcorper magna. 191| | 192| |""" /Users/travis/build/eneko/MarkdownGenerator/Tests/MarkdownGeneratorTests/PublicInterface/MarkdownTableTests.swift: 1| |// 2| |// MarkdownTableTests.swift 3| |// MarkdownGeneratorTests 4| |// 5| |// Created by Eneko Alonso on 10/9/17. 6| |// 7| | 8| |import XCTest 9| |import MarkdownGenerator 10| | 11| |class MarkdownTableTests: XCTestCase { 12| | 13| 1| func testEmptyTable() { 14| 1| let table = MarkdownTable(headers: [], data: []) 15| 1| XCTAssertEqual(table.markdown, "\n") 16| 1| } 17| | 18| 1| func test1x1Table() { 19| 1| let data: [[String]] = [[]] 20| 1| let table = MarkdownTable(headers: ["Header"], data: data) 21| 1| 22| 1| let output = """ 23| 1| | Header | 24| 1| | ------ | 25| 1| | | 26| 1| """ 27| 1| 28| 1| XCTAssertEqual(table.markdown, output) 29| 1| } 30| | 31| 1| func test3x3Table() { 32| 1| let data: [[String]] = [ 33| 1| ["🍏", "Apple", "Fruits"], 34| 1| ["🍊", "Orange", "Fruits"], 35| 1| ["πŸ₯–", "Bread", "Bakery"] 36| 1| ] 37| 1| let table = MarkdownTable(headers: ["", "Name", "Department"], data: data) 38| 1| 39| 1| let output = """ 40| 1| | | Name | Department | 41| 1| | -- | ------ | ---------- | 42| 1| | 🍏 | Apple | Fruits | 43| 1| | 🍊 | Orange | Fruits | 44| 1| | πŸ₯– | Bread | Bakery | 45| 1| """ 46| 1| 47| 1| XCTAssertEqual(table.markdown, output) 48| 1| } 49| | 50| 1| func testMultilineValues() { 51| 1| let data: [[String]] = [ 52| 1| ["Single-line value", "Multi-line\n\nvalue"], 53| 1| ["Single-line value", "Multi-line\n\nvalue"], 54| 1| ["Single-line value", "Multi-line\n\nvalue"] 55| 1| ] 56| 1| let table = MarkdownTable(headers: ["Single-line", "Multi-line"], data: data) 57| 1| 58| 1| let output = """ 59| 1| | Single-line | Multi-line | 60| 1| | ----------------- | ----------------- | 61| 1| | Single-line value | Multi-line value | 62| 1| | Single-line value | Multi-line value | 63| 1| | Single-line value | Multi-line value | 64| 1| """ 65| 1| 66| 1| XCTAssertEqual(table.markdown, output) 67| 1| } 68| | 69| 1| func testMixedTable() { 70| 1| let table = MarkdownTable(headers: ["Foo"], data: [["Bar"], [], ["Baz", "Bax"]]) 71| 1| 72| 1| let output = """ 73| 1| | Foo | | 74| 1| | --- | --- | 75| 1| | Bar | | 76| 1| | | | 77| 1| | Baz | Bax | 78| 1| """ 79| 1| 80| 1| XCTAssertEqual(table.markdown, output) 81| 1| } 82| | 83| | static var allTests = [ 84| | ("test1x1Table", test1x1Table), 85| | ("test3x3Table", test3x3Table), 86| | ("testMultilineValues", testMultilineValues) 87| | ] 88| | 89| |} <<<<<< EOF