Parse markdown oop

This commit is contained in:
Max Goedjen 2025-09-13 17:38:00 -07:00
parent b308b10716
commit 98e21ab449
No known key found for this signature in database
3 changed files with 59 additions and 2 deletions

View File

@ -1,4 +1,5 @@
import Foundation
import SwiftUI
/// A release is a representation of a downloadable update.
public struct Release: Codable, Sendable {
@ -15,6 +16,8 @@ public struct Release: Codable, Sendable {
/// A user-facing description of the contents of the update.
public let body: String
public let attributedBody: AttributedString
/// Initializes a Release.
/// - Parameters:
/// - name: The user-facing name of the release.
@ -26,6 +29,56 @@ public struct Release: Codable, Sendable {
self.prerelease = prerelease
self.html_url = html_url
self.body = body
self.attributedBody = AttributedString(_markdown: body)
}
public init(_ release: GitHubRelease) {
self.name = release.name
self.prerelease = release.prerelease
self.html_url = release.html_url
self.body = release.body
self.attributedBody = AttributedString(_markdown: release.body)
}
}
public struct GitHubRelease: Codable, Sendable {
let name: String
let prerelease: Bool
let html_url: URL
let body: String
}
fileprivate extension AttributedString {
init(_markdown markdown: String) {
let split = markdown.split(whereSeparator: \.isNewline)
let lines = split
.compactMap {
try? AttributedString(markdown: String($0), options: .init(allowsExtendedAttributes: true, interpretedSyntax: .full))
}
.map { (string: AttributedString) in
guard case let .header(level) = string.runs.first?.presentationIntent?.components.first?.kind else { return string }
return AttributedString("\n") + string
.transformingAttributes(\.font) { font in
font.value = switch level {
case 2: .title3.bold()
case 3: .title3
default: .body
}
}
.transformingAttributes(\.underlineStyle) { underline in
underline.value = switch level {
case 2: .single
default: .none
}
}
+ AttributedString("\n")
}
self = lines.reduce(into: AttributedString()) { partialResult, next in
partialResult.append(next)
partialResult.append(AttributedString("\n"))
}
}
}

View File

@ -34,7 +34,9 @@ public final class XPCServiceDelegate: NSObject, NSXPCListenerDelegate {
if let error = error as? Codable & Error {
reply(nil, NSError(error))
} else {
reply(nil, error)
// Sending cast directly tries to serialize it and crashes XPCEncoder.
let cast = error as NSError
reply(nil, NSError(domain: cast.domain, code: cast.code, userInfo: [NSLocalizedDescriptionKey: error.localizedDescription]))
}
}
}

View File

@ -11,7 +11,9 @@ final class SecretiveUpdater: NSObject, XPCProtocol {
func process(_: Data) async throws -> [Release] {
let (data, _) = try await URLSession.shared.data(from: Constants.updateURL)
return try JSONDecoder().decode([Release].self, from: data)
return try JSONDecoder()
.decode([GitHubRelease].self, from: data)
.map(Release.init)
}
}