Skip to content

Commit bb30e78

Browse files
committed
Create PersistableConvertible
1 parent 0b17a37 commit bb30e78

11 files changed

+325
-238
lines changed

CotEditor/Sources/Models/Persistable.swift

Lines changed: 0 additions & 54 deletions
This file was deleted.

CotEditor/Sources/Models/PortableSettingsDocument.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ import SwiftUI
2727
import UniformTypeIdentifiers
2828
import SemanticVersioning
2929

30+
extension UTType {
31+
32+
static let cotSettings = UTType(exportedAs: "com.coteditor.CotEditor.settings")
33+
}
34+
35+
3036
struct PortableSettingsDocument: FileDocument {
3137

3238
struct Info: Equatable, Codable {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//
2+
// MultipleReplace+Persistable.swift
3+
//
4+
// CotEditor
5+
// https://coteditor.com
6+
//
7+
// Created by imanishi on 1/10/26.
8+
//
9+
// ---------------------------------------------------------------------------
10+
//
11+
// © 2026 CotEditor Project
12+
//
13+
// Licensed under the Apache License, Version 2.0 (the "License");
14+
// you may not use this file except in compliance with the License.
15+
// You may obtain a copy of the License at
16+
//
17+
// https://www.apache.org/licenses/LICENSE-2.0
18+
//
19+
// Unless required by applicable law or agreed to in writing, software
20+
// distributed under the License is distributed on an "AS IS" BASIS,
21+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22+
// See the License for the specific language governing permissions and
23+
// limitations under the License.
24+
//
25+
26+
import Foundation
27+
import UniformTypeIdentifiers
28+
import TextFind
29+
30+
extension UTType {
31+
32+
static let cotReplacement = UTType(exportedAs: "com.coteditor.CotEditor.replacement")
33+
}
34+
35+
36+
extension MultipleReplace: PersistableConvertible {
37+
38+
typealias Persistent = Data
39+
40+
41+
nonisolated static let fileType: UTType = .cotReplacement
42+
43+
44+
init(persistence: any Persistable, type: UTType) throws {
45+
46+
switch persistence {
47+
case let data as Data where type.conforms(to: .cotReplacement):
48+
self = try JSONDecoder().decode(Self.self, from: data)
49+
50+
case let data as Data where type.conforms(to: .tabSeparatedText):
51+
guard let string = String(data: data, encoding: .utf8) else { throw CocoaError(.fileReadInapplicableStringEncoding) }
52+
53+
try self.init(tabSeparatedText: string)
54+
55+
default:
56+
throw CocoaError(.fileReadUnsupportedScheme)
57+
}
58+
}
59+
60+
61+
nonisolated static func persistence(at fileURL: URL) throws -> Persistent {
62+
63+
try Data(contentsOf: fileURL)
64+
}
65+
66+
67+
func makePersistable() throws -> any Persistable {
68+
69+
let encoder = JSONEncoder()
70+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
71+
72+
return try encoder.encode(self)
73+
}
74+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//
2+
// Persistable.swift
3+
//
4+
// CotEditor
5+
// https://coteditor.com
6+
//
7+
// Created by 1024jp on 2026-01-10.
8+
//
9+
// ---------------------------------------------------------------------------
10+
//
11+
// © 2026 1024jp
12+
//
13+
// Licensed under the Apache License, Version 2.0 (the "License");
14+
// you may not use this file except in compliance with the License.
15+
// You may obtain a copy of the License at
16+
//
17+
// https://www.apache.org/licenses/LICENSE-2.0
18+
//
19+
// Unless required by applicable law or agreed to in writing, software
20+
// distributed under the License is distributed on an "AS IS" BASIS,
21+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22+
// See the License for the specific language governing permissions and
23+
// limitations under the License.
24+
//
25+
26+
import Foundation
27+
import UniformTypeIdentifiers
28+
29+
protocol PersistableConvertible: Sendable {
30+
31+
associatedtype Persistence: Persistable
32+
33+
34+
/// The default uniform type identifier for files representing this type.
35+
nonisolated static var fileType: UTType { get }
36+
37+
38+
/// Creates an instance from a persistable payload and file type.
39+
init(persistence: any Persistable, type: UTType) throws
40+
41+
/// Loads the persisted payload from a file.
42+
nonisolated static func persistence(at fileURL: URL) throws -> Persistence
43+
44+
/// Produces a persistable payload that represents the current value.
45+
func makePersistable() throws -> any Persistable
46+
}
47+
48+
49+
extension PersistableConvertible {
50+
51+
/// Creates an instance by loading contents from a file.
52+
///
53+
/// - Parameter fileURL: The location of the file to read.
54+
/// - Throws: An error if reading or decoding the file fails.
55+
init(contentsOf fileURL: URL) throws {
56+
57+
let persistence = try Self.persistence(at: fileURL)
58+
59+
try self.init(persistence: persistence, type: UTType(filenameExtension: fileURL.pathExtension) ?? Self.fileType)
60+
}
61+
}
62+
63+
64+
// MARK: -
65+
66+
protocol Persistable: Equatable, Sendable {
67+
68+
/// The `FileWrapper` representation.
69+
var fileWrapper: FileWrapper { get }
70+
71+
72+
/// Writes the contents to the specified file location.
73+
///
74+
/// - Parameter fileURL: The destination URL to write the persisted data to.
75+
/// - Throws: An error if writing the contents fails.
76+
func persist(to fileURL: URL) throws
77+
}
78+
79+
80+
extension Data: Persistable {
81+
82+
var fileWrapper: FileWrapper {
83+
84+
FileWrapper(regularFileWithContents: self)
85+
}
86+
87+
88+
func persist(to fileURL: URL) throws {
89+
90+
try self.write(to: fileURL)
91+
}
92+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// Syntax+Persistable.swift
3+
//
4+
// CotEditor
5+
// https://coteditor.com
6+
//
7+
// Created by imanishi on 1/10/26.
8+
//
9+
// ---------------------------------------------------------------------------
10+
//
11+
// © 2026 CotEditor Project
12+
//
13+
// Licensed under the Apache License, Version 2.0 (the "License");
14+
// you may not use this file except in compliance with the License.
15+
// You may obtain a copy of the License at
16+
//
17+
// https://www.apache.org/licenses/LICENSE-2.0
18+
//
19+
// Unless required by applicable law or agreed to in writing, software
20+
// distributed under the License is distributed on an "AS IS" BASIS,
21+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22+
// See the License for the specific language governing permissions and
23+
// limitations under the License.
24+
//
25+
26+
import Foundation
27+
import UniformTypeIdentifiers
28+
import Syntax
29+
import Yams
30+
31+
extension Syntax: PersistableConvertible {
32+
33+
typealias Persistent = Data
34+
35+
36+
nonisolated static let fileType: UTType = .yaml
37+
38+
39+
init(persistence: any Persistable, type: UTType) throws {
40+
41+
switch persistence {
42+
case let data as Data where type.conforms(to: .yaml):
43+
self = try YAMLDecoder().decode(Self.self, from: data)
44+
45+
default:
46+
throw CocoaError(.fileReadUnsupportedScheme)
47+
}
48+
}
49+
50+
51+
nonisolated static func persistence(at fileURL: URL) throws -> Persistent {
52+
53+
try Data(contentsOf: fileURL)
54+
}
55+
56+
57+
func makePersistable() throws -> any Persistable {
58+
59+
let encoder = YAMLEncoder()
60+
encoder.options.allowUnicode = true
61+
encoder.options.sortKeys = true
62+
63+
let yamlString = try encoder.encode(self)
64+
65+
return Data(yamlString.utf8)
66+
}
67+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// Theme+Persistable.swift
3+
//
4+
// CotEditor
5+
// https://coteditor.com
6+
//
7+
// Created by imanishi on 1/10/26.
8+
//
9+
// ---------------------------------------------------------------------------
10+
//
11+
// © 2026 CotEditor Project
12+
//
13+
// Licensed under the Apache License, Version 2.0 (the "License");
14+
// you may not use this file except in compliance with the License.
15+
// You may obtain a copy of the License at
16+
//
17+
// https://www.apache.org/licenses/LICENSE-2.0
18+
//
19+
// Unless required by applicable law or agreed to in writing, software
20+
// distributed under the License is distributed on an "AS IS" BASIS,
21+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22+
// See the License for the specific language governing permissions and
23+
// limitations under the License.
24+
//
25+
26+
import Foundation
27+
import UniformTypeIdentifiers
28+
29+
extension UTType {
30+
31+
static let cotTheme = UTType(exportedAs: "com.coteditor.CotEditor.theme")
32+
}
33+
34+
35+
extension Theme: PersistableConvertible, @unchecked Sendable {
36+
37+
typealias Persistent = Data
38+
39+
40+
nonisolated static let fileType: UTType = .cotTheme
41+
42+
43+
init(persistence: any Persistable, type: UTType) throws {
44+
45+
switch persistence {
46+
case let data as Data where type.conforms(to: UTType.cotTheme):
47+
self = try JSONDecoder().decode(Self.self, from: data)
48+
49+
default:
50+
throw CocoaError(.fileReadUnsupportedScheme)
51+
}
52+
}
53+
54+
55+
nonisolated static func persistence(at fileURL: URL) throws -> Persistent {
56+
57+
try Data(contentsOf: fileURL)
58+
}
59+
60+
61+
func makePersistable() throws -> any Persistable {
62+
63+
let encoder = JSONEncoder()
64+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
65+
66+
return try encoder.encode(self)
67+
}
68+
}

0 commit comments

Comments
 (0)