spiegel-keyman/mac/KeymanSettings/Sources/Persistence/Data/PackageSource.swift

177 lines
3.7 KiB
Swift

/*
* Keyman is copyright (C) SIL Global. MIT License.
*
* Created by Shawn Schantz on 2025-12-19
*
* Value object for loading a package from kmp.json
*
*/
import Foundation
public struct PackageSource: Identifiable, Decodable, Hashable, Equatable {
public var id = UUID()
var directoryUrl: URL?
var kmpJsonFileUrl: URL?
let system: SystemInfo?
let options: Options?
let info: Info
let files: [PackageFile]?
let keyboards: [KeyboardSource]?
// computed properties for convenience
var packageName: String {
return info.name.description
}
var packageVersion: String {
return info.version.description
}
var copyright: String? {
if let copy = info.copyright?.description {
return copy
} else {
return nil
}
}
var readmeFilename: String? {
if let filename = options?.readmeFile {
return filename
} else {
return nil
}
}
var helpFilename: String? {
if let filename = options?.welcomeFile {
return filename
} else {
return nil
}
}
var graphicFilename: String? {
if let filename = options?.graphicFile {
return filename
} else {
return nil
}
}
enum CodingKeys: String, CodingKey {
case system
case options
case info
case files
case keyboards
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
directoryUrl = nil
kmpJsonFileUrl = nil
self.info = try container.decode(Info.self, forKey: .info)
self.keyboards = try container.decodeIfPresent([KeyboardSource].self, forKey: .keyboards)
self.system = try container.decodeIfPresent(SystemInfo.self, forKey: .system)
self.options = try container.decodeIfPresent(Options.self, forKey: .options)
self.files = try container.decodeIfPresent([PackageFile].self, forKey: .files)
if files?.isEmpty ?? true {
throw LoadPackageError.containsNoFiles
}
if keyboards?.isEmpty ?? true {
throw LoadPackageError.containsNoKeyboards
}
}
public func hash(into hasher: inout Hasher) {
hasher.combine(id) // only combine the unique ID
}
// Custom Equatable conformance (required by Hashable)
public static func == (lhs: PackageSource, rhs: PackageSource) -> Bool {
return lhs.id == rhs.id // only compare unique IDs
}
}
struct Info: Decodable {
let name: DescribedValue
let version: DescribedValue
let copyright: DescribedValue?
let author: Author?
let website: Website?
enum CodingKeys: String, CodingKey {
case name
case copyright
case author
case version
case website
}
}
struct DescribedValue: Decodable {
let description: String
enum CodingKeys: String, CodingKey {
case description
}
}
struct Author: Decodable {
let description: String?
let url: String?
enum CodingKeys: String, CodingKey {
case description
case url
}
}
struct Website: Decodable {
let description: String?
let url: String?
enum CodingKeys: String, CodingKey {
case description
case url
}
}
struct SystemInfo: Decodable {
let keymanDeveloperVersion: String?
let fileVersion: String?
enum CodingKeys: String, CodingKey {
case keymanDeveloperVersion
case fileVersion
}
}
struct Options: Decodable {
let readmeFile: String?
let graphicFile: String?
let licenseFile: String?
let welcomeFile: String?
enum CodingKeys: String, CodingKey {
case readmeFile
case graphicFile
case licenseFile
case welcomeFile
}
}
struct PackageFile: Decodable {
let name: String?
let description: String?
enum CodingKeys: String, CodingKey {
case name
case description
}
}