SwiftGemtext.swift (3227B)
1 import SwiftUI 2 3 public struct Gemtext { 4 public init() {} 5 6 public func parse(_ source: String) -> [LineType] { 7 return parse(source, url: nil) 8 } 9 10 public func parse(_ source: String, url: URL?) -> [LineType] { 11 var lines = [LineType]() 12 var pre = PreformattedState() 13 14 for line in source.lines() { 15 if pre.active { 16 if line.starts(with: "```") { 17 lines.append(pre.getLineType()) 18 pre = PreformattedState() 19 continue 20 } 21 22 pre.lines.append(line) 23 } else { 24 if line.starts(with: "```") { 25 let alt = line.dropFirst(3).trimmingCharacters(in: .whitespacesAndNewlines) 26 if alt.count > 0 { pre.alt = alt } 27 pre.active = true 28 continue 29 } 30 31 lines.append(parsePlainLine(line, relativeTo: url)) 32 } 33 } 34 return lines 35 } 36 37 func parsePlainLine(_ line: String, relativeTo rel: URL?) -> LineType { 38 if line.starts(with: "#") { // Heading Line 39 var level: Int = 1 40 if line.starts(with: "###") { 41 level = 3 42 } else if line.starts(with: "##") { 43 level = 2 44 } 45 return LineType.Heading(level, line.dropFirst(level).trimmingCharacters(in: .whitespacesAndNewlines)) 46 } else if line.starts(with: "=>") { // Link Line 47 let base = line.dropFirst(2).trimmingCharacters(in: .whitespacesAndNewlines) 48 let url = base.components(separatedBy: .whitespaces).first! 49 let caption = base.dropFirst(url.count).trimmingCharacters(in: .whitespaces) 50 51 return LineType.Link( 52 URL(string: url.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed)!, relativeTo: rel)!, 53 caption.isEmpty ? nil : caption 54 ) 55 } else if line.starts(with: "* ") { // Unordered List Line 56 return LineType.UnorderedList(line.dropFirst(2).trimmingCharacters(in: .whitespacesAndNewlines)) 57 } else if line.starts(with: ">") { // Quote Line 58 return LineType.Quote(line.dropFirst().trimmingCharacters(in: .whitespacesAndNewlines)) 59 } else if line.trimmingCharacters(in: .whitespacesAndNewlines) == "" { // Empty Line 60 return LineType.EmptyLine 61 } else { // Text Line 62 return LineType.Text(line) 63 } 64 } 65 } 66 67 struct PreformattedState { 68 var lines = [String]() 69 var active = false 70 var alt: String? 71 72 func getLineType() -> LineType { 73 let str = self.lines.joined(separator: "\r\n") 74 return LineType.PreformattedText(str, alt) 75 } 76 } 77 78 public enum LineType: Hashable { 79 case Text(String) 80 case Link(URL, String?) 81 case PreformattedText(String, String?) 82 case Heading(Int, String) 83 case UnorderedList(String) 84 case Quote(String) 85 case EmptyLine 86 } 87 88 extension StringProtocol { 89 func lines() -> [String] { 90 var lines = [String]() 91 self.enumerateLines { line, _ in 92 lines.append(line) 93 } 94 return lines 95 } 96 }