Commit 1c3d19b233
Unsigned
Layout: unified · split
DomainReportExporter.swift +29 −2
| @@ -77,14 +77,23 @@ enum DomainReportExporter { | |||
| 77 | let line = lines[index] | 77 | let line = lines[index] |
| 78 | if index == 0, line == title { | 78 | if index == 0, line == title { |
| 79 | index += 1 | 79 | index += 1 |
| 80 | if index < lines.count, isUnderline(lines[index], for: line) { | ||
| 81 | index += 1 | ||
| 82 | } | ||
| 80 | continue | 83 | continue |
| 81 | } | 84 | } |
| 82 | if index + 1 < lines.count, !line.isEmpty, lines[index + 1] == String(repeating: "-", count: line.count) { | 85 | if index + 1 < lines.count, !line.isEmpty, isUnderline(lines[index + 1], for: line) { |
| 83 | output.append("") | 86 | output.append("") |
| 84 | output.append("## \(line)") | 87 | output.append("## \(line)") |
| 85 | index += 2 | 88 | index += 2 |
| 86 | continue | 89 | continue |
| 87 | } | 90 | } |
| 91 | if isRule(line) { | ||
| 92 | output.append("") | ||
| 93 | output.append("---") | ||
| 94 | index += 1 | ||
| 95 | continue | ||
| 96 | } | ||
| 88 | if line.isEmpty || line.hasPrefix("-") || line.hasPrefix(" ") { | 97 | if line.isEmpty || line.hasPrefix("-") || line.hasPrefix(" ") { |
| 89 | output.append(line) | 98 | output.append(line) |
| 90 | } else { | 99 | } else { |
| @@ -95,6 +104,21 @@ enum DomainReportExporter { | |||
| 95 | return output.joined(separator: "\n") | 104 | return output.joined(separator: "\n") |
| 96 | } | 105 | } |
| 97 | 106 | ||
| 107 | /// True when `line` is a run of `-` or `=` exactly as long as the heading it | ||
| 108 | /// underlines. `batchText` uses `=` for the document title, `appendSection` | ||
| 109 | /// uses `-` for section headers. | ||
| 110 | private static func isUnderline(_ line: String, for heading: String) -> Bool { | ||
| 111 | guard !heading.isEmpty else { return false } | ||
| 112 | return line == String(repeating: "-", count: heading.count) | ||
| 113 | || line == String(repeating: "=", count: heading.count) | ||
| 114 | } | ||
| 115 | |||
| 116 | /// True for a standalone divider not attached to a heading — `batchText` | ||
| 117 | /// emits a fixed 48-character `=` run between reports. | ||
| 118 | private static func isRule(_ line: String) -> Bool { | ||
| 119 | line.count >= 3 && (line.allSatisfy { $0 == "=" } || line.allSatisfy { $0 == "-" }) | ||
| 120 | } | ||
| 121 | |||
| 98 | /// Renders Markdown as a simple monospaced multi-page PDF. Foundation-only | 122 | /// Renders Markdown as a simple monospaced multi-page PDF. Foundation-only |
| 99 | /// consumers (no UIKit available) get the Markdown bytes back instead. | 123 | /// consumers (no UIKit available) get the Markdown bytes back instead. |
| 100 | static func pdfData(fromMarkdown markdown: String) -> Data { | 124 | static func pdfData(fromMarkdown markdown: String) -> Data { |
| @@ -249,7 +273,10 @@ enum DomainReportExporter { | |||
| 249 | } else { | 273 | } else { |
| 250 | dnsLines.append("Records:") | 274 | dnsLines.append("Records:") |
| 251 | for section in report.dns.recordSections { | 275 | for section in report.dns.recordSections { |
| 252 | let values = (section.records + section.wildcardRecords).map(\.value) | 276 | var seen: Set<String> = [] |
| 277 | let values = (section.records + section.wildcardRecords) | ||
| 278 | .map(\.value) | ||
| 279 | .filter { seen.insert($0).inserted } | ||
| 253 | let renderedValues = values.isEmpty ? "None" : values.joined(separator: " | ") | 280 | let renderedValues = values.isEmpty ? "None" : values.joined(separator: " | ") |
| 254 | dnsLines.append(" \(section.recordType.rawValue): \(renderedValues)") | 281 | dnsLines.append(" \(section.recordType.rawValue): \(renderedValues)") |
| 255 | } | 282 | } |