From 5c4b503ecb4f81bcc9a3f3982ba1876e17cdb766 Mon Sep 17 00:00:00 2001 From: Ying Zhong <0x00eeee@gmail.com> Date: Thu, 27 Aug 2026 16:06:31 +0800 Subject: [PATCH] Migrate json configuration files --- TextGrabber2/Resources/Services/en-US.json | 71 ++++++++++---------- TextGrabber2/Resources/Services/zh-Hans.json | 71 ++++++++++---------- TextGrabber2/Resources/Services/zh-Hant.json | 71 ++++++++++---------- TextGrabber2/Sources/ConfigurationFile.swift | 46 +++++++++++++ TextGrabber2/Sources/ContentFilters.swift | 16 ++++- TextGrabber2/Sources/KeyBindings.swift | 16 ++++- TextGrabber2/Sources/Services.swift | 10 ++- 7 files changed, 191 insertions(+), 110 deletions(-) create mode 100644 TextGrabber2/Sources/ConfigurationFile.swift diff --git a/TextGrabber2/Resources/Services/en-US.json b/TextGrabber2/Resources/Services/en-US.json index de09f89..2ff4d16 100644 --- a/TextGrabber2/Resources/Services/en-US.json +++ b/TextGrabber2/Resources/Services/en-US.json @@ -1,34 +1,37 @@ -[ - { - "serviceName": "Look Up in Dictionary", - "displayName": "Look Up in Dictionary" - }, - { - "serviceName": "Search With %WebSearchProvider@", - "displayName": "Search in Safari" - }, - { - "serviceName": "SEARCH_WITH_SPOTLIGHT", - "displayName": "Search with Spotlight" - }, - { - "serviceName": "Convert Text from Simplified to Traditional Chinese", - "displayName": "Convert to Traditional Chinese" - }, - { - "serviceName": "Convert Text from Traditional to Simplified Chinese", - "displayName": "Convert to Simplified Chinese" - }, - { - "serviceName": "New TextEdit Window Containing Selection", - "displayName": "New File in TextEdit" - }, - { - "serviceName": "Make Sticky", - "displayName": "Make New Sticky Note" - }, - { - "serviceName": "Summarize", - "displayName": "Summarize" - } -] \ No newline at end of file +{ + "$schema": "https://raw.githubusercontent.com/TextGrabber2-app/schemas/main/services.json", + "items": [ + { + "serviceName": "Look Up in Dictionary", + "displayName": "Look Up in Dictionary" + }, + { + "serviceName": "Search With %WebSearchProvider@", + "displayName": "Search in Safari" + }, + { + "serviceName": "SEARCH_WITH_SPOTLIGHT", + "displayName": "Search with Spotlight" + }, + { + "serviceName": "Convert Text from Simplified to Traditional Chinese", + "displayName": "Convert to Traditional Chinese" + }, + { + "serviceName": "Convert Text from Traditional to Simplified Chinese", + "displayName": "Convert to Simplified Chinese" + }, + { + "serviceName": "New TextEdit Window Containing Selection", + "displayName": "New File in TextEdit" + }, + { + "serviceName": "Make Sticky", + "displayName": "Make New Sticky Note" + }, + { + "serviceName": "Summarize", + "displayName": "Summarize" + } + ] +} diff --git a/TextGrabber2/Resources/Services/zh-Hans.json b/TextGrabber2/Resources/Services/zh-Hans.json index a60dbd7..8062ba0 100644 --- a/TextGrabber2/Resources/Services/zh-Hans.json +++ b/TextGrabber2/Resources/Services/zh-Hans.json @@ -1,34 +1,37 @@ -[ - { - "serviceName": "Look Up in Dictionary", - "displayName": "在词典中查询" - }, - { - "serviceName": "Search With %WebSearchProvider@", - "displayName": "在 Safari 中搜索" - }, - { - "serviceName": "SEARCH_WITH_SPOTLIGHT", - "displayName": "用 Spotlight 搜索" - }, - { - "serviceName": "Convert Text from Simplified to Traditional Chinese", - "displayName": "转换为繁体中文" - }, - { - "serviceName": "Convert Text from Traditional to Simplified Chinese", - "displayName": "转换为简体中文" - }, - { - "serviceName": "New TextEdit Window Containing Selection", - "displayName": "在 TextEdit 新建文件" - }, - { - "serviceName": "Make Sticky", - "displayName": "创建新便笺条" - }, - { - "serviceName": "Summarize", - "displayName": "摘要" - } -] \ No newline at end of file +{ + "$schema": "https://raw.githubusercontent.com/TextGrabber2-app/schemas/main/services.json", + "items": [ + { + "serviceName": "Look Up in Dictionary", + "displayName": "在词典中查询" + }, + { + "serviceName": "Search With %WebSearchProvider@", + "displayName": "在 Safari 中搜索" + }, + { + "serviceName": "SEARCH_WITH_SPOTLIGHT", + "displayName": "用 Spotlight 搜索" + }, + { + "serviceName": "Convert Text from Simplified to Traditional Chinese", + "displayName": "转换为繁体中文" + }, + { + "serviceName": "Convert Text from Traditional to Simplified Chinese", + "displayName": "转换为简体中文" + }, + { + "serviceName": "New TextEdit Window Containing Selection", + "displayName": "在 TextEdit 新建文件" + }, + { + "serviceName": "Make Sticky", + "displayName": "创建新便笺条" + }, + { + "serviceName": "Summarize", + "displayName": "摘要" + } + ] +} diff --git a/TextGrabber2/Resources/Services/zh-Hant.json b/TextGrabber2/Resources/Services/zh-Hant.json index 2ea49e3..cebfebe 100644 --- a/TextGrabber2/Resources/Services/zh-Hant.json +++ b/TextGrabber2/Resources/Services/zh-Hant.json @@ -1,34 +1,37 @@ -[ - { - "serviceName": "Look Up in Dictionary", - "displayName": "在辭典中查詢" - }, - { - "serviceName": "Search With %WebSearchProvider@", - "displayName": "在 Safari 中搜尋" - }, - { - "serviceName": "SEARCH_WITH_SPOTLIGHT", - "displayName": "用 Spotlight 搜尋" - }, - { - "serviceName": "Convert Text from Simplified to Traditional Chinese", - "displayName": "轉換為繁體中文" - }, - { - "serviceName": "Convert Text from Traditional to Simplified Chinese", - "displayName": "轉換為簡體中文" - }, - { - "serviceName": "New TextEdit Window Containing Selection", - "displayName": "在 TextEdit 新建檔案" - }, - { - "serviceName": "Make Sticky", - "displayName": "建立新便條紙" - }, - { - "serviceName": "Summarize", - "displayName": "摘要" - } -] \ No newline at end of file +{ + "$schema": "https://raw.githubusercontent.com/TextGrabber2-app/schemas/main/services.json", + "items": [ + { + "serviceName": "Look Up in Dictionary", + "displayName": "在辭典中查詢" + }, + { + "serviceName": "Search With %WebSearchProvider@", + "displayName": "在 Safari 中搜尋" + }, + { + "serviceName": "SEARCH_WITH_SPOTLIGHT", + "displayName": "用 Spotlight 搜尋" + }, + { + "serviceName": "Convert Text from Simplified to Traditional Chinese", + "displayName": "轉換為繁體中文" + }, + { + "serviceName": "Convert Text from Traditional to Simplified Chinese", + "displayName": "轉換為簡體中文" + }, + { + "serviceName": "New TextEdit Window Containing Selection", + "displayName": "在 TextEdit 新建檔案" + }, + { + "serviceName": "Make Sticky", + "displayName": "建立新便條紙" + }, + { + "serviceName": "Summarize", + "displayName": "摘要" + } + ] +} diff --git a/TextGrabber2/Sources/ConfigurationFile.swift b/TextGrabber2/Sources/ConfigurationFile.swift new file mode 100644 index 0000000..a16a23c --- /dev/null +++ b/TextGrabber2/Sources/ConfigurationFile.swift @@ -0,0 +1,46 @@ +// +// ConfigurationFile.swift +// TextGrabber2 +// +// Created by cyan on 2026/8/27. +// + +import Foundation + +enum SchemaURLs { + static let contentFilters = "https://raw.githubusercontent.com/TextGrabber2-app/schemas/main/content-filters.json" + static let keyBindings = "https://raw.githubusercontent.com/TextGrabber2-app/schemas/main/key-bindings.json" + static let services = "https://raw.githubusercontent.com/TextGrabber2-app/schemas/main/services.json" +} + +struct ConfigurationFile: Decodable { + let items: [Item] + + init(from decoder: Decoder) throws { + if let items = try? decoder.singleValueContainer().decode([Item].self) { + self.items = items + return + } + + let container = try decoder.container(keyedBy: CodingKeys.self) + items = try container.decode([Item].self, forKey: .items) + } + + static func migrate(at fileURL: URL, schemaURL: String) throws { + let data = try Data(contentsOf: fileURL) + guard try JSONSerialization.jsonObject(with: data) is [Any], + let json = String(data: data, encoding: .utf8) else { + return + } + + let items = json.trimmingCharacters(in: .whitespacesAndNewlines) + .replacingOccurrences(of: "\n", with: "\n ") + let migratedJSON = "{\n \"$schema\": \"\(schemaURL)\",\n \"items\": \(items)\n}\n" + let migratedData = Data(migratedJSON.utf8) + try migratedData.write(to: fileURL, options: .atomic) + } + + private enum CodingKeys: String, CodingKey { + case items + } +} diff --git a/TextGrabber2/Sources/ContentFilters.swift b/TextGrabber2/Sources/ContentFilters.swift index a2fa4ce..48d9cc5 100644 --- a/TextGrabber2/Sources/ContentFilters.swift +++ b/TextGrabber2/Sources/ContentFilters.swift @@ -21,12 +21,22 @@ enum ContentFilters { } static func initialize() { - guard !FileManager.default.fileExists(atPath: fileURL.path(percentEncoded: false)) else { + if FileManager.default.fileExists(atPath: fileURL.path(percentEncoded: false)) { + do { + try ConfigurationFile.migrate(at: fileURL, schemaURL: SchemaURLs.contentFilters) + } catch { + Logger.log(.error, "Failed to migrate \(Constants.fileName): \(error)") + } + return Logger.log(.info, "\(Constants.fileName) was created before") } do { - try "[]\n".write(to: fileURL, atomically: true, encoding: .utf8) + try "{\n \"$schema\": \"\(SchemaURLs.contentFilters)\",\n \"items\": []\n}\n".write( + to: fileURL, + atomically: true, + encoding: .utf8 + ) } catch { Logger.log(.error, "\(error)") } @@ -152,7 +162,7 @@ private extension ContentFilters { return [] } - guard let rules = try? JSONDecoder().decode([Rule].self, from: data) else { + guard let rules = try? JSONDecoder().decode(ConfigurationFile.self, from: data).items else { Logger.log(.error, "Failed to decode the file") return [] } diff --git a/TextGrabber2/Sources/KeyBindings.swift b/TextGrabber2/Sources/KeyBindings.swift index 3b650bd..b1bce26 100644 --- a/TextGrabber2/Sources/KeyBindings.swift +++ b/TextGrabber2/Sources/KeyBindings.swift @@ -15,12 +15,22 @@ enum KeyBindings { } static func initialize() { - guard !FileManager.default.fileExists(atPath: fileURL.path(percentEncoded: false)) else { + if FileManager.default.fileExists(atPath: fileURL.path(percentEncoded: false)) { + do { + try ConfigurationFile.migrate(at: fileURL, schemaURL: SchemaURLs.keyBindings) + } catch { + Logger.log(.error, "Failed to migrate \(Constants.fileName): \(error)") + } + return Logger.log(.info, "\(Constants.fileName) was created before") } do { - try "[]\n".write(to: fileURL, atomically: true, encoding: .utf8) + try "{\n \"$schema\": \"\(SchemaURLs.keyBindings)\",\n \"items\": []\n}\n".write( + to: fileURL, + atomically: true, + encoding: .utf8 + ) } catch { Logger.log(.error, "\(error)") } @@ -32,7 +42,7 @@ enum KeyBindings { return [] } - guard let items = try? JSONDecoder().decode([Item].self, from: data) else { + guard let items = try? JSONDecoder().decode(ConfigurationFile.self, from: data).items else { Logger.log(.error, "Failed to decode the file") return [] } diff --git a/TextGrabber2/Sources/Services.swift b/TextGrabber2/Sources/Services.swift index 8bb7704..5aed2c7 100644 --- a/TextGrabber2/Sources/Services.swift +++ b/TextGrabber2/Sources/Services.swift @@ -31,7 +31,7 @@ enum Services { return [] } - guard let items = try? JSONDecoder().decode([Item].self, from: data) else { + guard let items = try? JSONDecoder().decode(ConfigurationFile.self, from: data).items else { Logger.log(.error, "Failed to decode the file") return [] } @@ -40,7 +40,13 @@ enum Services { }() static func initialize() { - guard !FileManager.default.fileExists(atPath: fileURL.path(percentEncoded: false)) else { + if FileManager.default.fileExists(atPath: fileURL.path(percentEncoded: false)) { + do { + try ConfigurationFile.migrate(at: fileURL, schemaURL: SchemaURLs.services) + } catch { + Logger.log(.error, "Failed to migrate \(Constants.fileName): \(error)") + } + return Logger.log(.info, "\(Constants.fileName) was created before") }