···
1
1
+
import Foundation
2
2
+
3
3
+
public struct PairFileSystemComponent<First: FileTreeReader, Second: FileTreeReader>: FileTreeReader {
4
4
+
public var first: First
5
5
+
public var second: Second
6
6
+
7
7
+
@inlinable public init(_ first: First, _ second: Second) {
8
8
+
self.first = first
9
9
+
self.second = second
10
10
+
}
11
11
+
12
12
+
public typealias Content = (First.Content, Second.Content)
13
13
+
14
14
+
public func read(from url: URL) throws -> FileTreeResult<Content> {
15
15
+
let firstResult = try first.read(from: url)
16
16
+
let secondResult = try second.read(from: url)
17
17
+
var diagnostics = DiagnosticReport<FileTreeLocation>()
18
18
+
diagnostics.append(contentsOf: firstResult.diagnostics)
19
19
+
diagnostics.append(contentsOf: secondResult.diagnostics)
20
20
+
var graph = SourceGraph()
21
21
+
graph.append(firstResult.graph)
22
22
+
graph.append(secondResult.graph)
23
23
+
24
24
+
switch (firstResult.output, secondResult.output) {
25
25
+
case let (.value(firstOutput), .value(secondOutput)):
26
26
+
return .value((firstOutput, secondOutput), graph: graph, diagnostics: diagnostics)
27
27
+
case (.value, .invalid), (.invalid, .value), (.invalid, .invalid):
28
28
+
return .invalid(graph: graph, diagnostics: diagnostics)
29
29
+
}
30
30
+
}
31
31
+
}
32
32
+
33
33
+
extension PairFileSystemComponent: FileTreeWriter where First: FileTreeWriter, Second: FileTreeWriter {
34
34
+
public func write(_ content: Content, to url: URL) throws -> FileTreeResult<Content> {
35
35
+
let firstResult = try first.write(content.0, to: url)
36
36
+
let secondResult = try second.write(content.1, to: url)
37
37
+
return results(firstResult, secondResult)
38
38
+
}
39
39
+
40
40
+
public func write(
41
41
+
_ content: Content,
42
42
+
to url: URL,
43
43
+
context: FileTreeWriteContext
44
44
+
) throws -> FileTreeResult<Content> {
45
45
+
let firstResult = try first.write(content.0, to: url, context: context)
46
46
+
let secondResult = try second.write(content.1, to: url, context: context)
47
47
+
return results(firstResult, secondResult)
48
48
+
}
49
49
+
50
50
+
private func results(
51
51
+
_ firstResult: FileTreeResult<First.Content>,
52
52
+
_ secondResult: FileTreeResult<Second.Content>
53
53
+
) -> FileTreeResult<Content> {
54
54
+
var diagnostics = DiagnosticReport<FileTreeLocation>()
55
55
+
diagnostics.append(contentsOf: firstResult.diagnostics)
56
56
+
diagnostics.append(contentsOf: secondResult.diagnostics)
57
57
+
var graph = SourceGraph()
58
58
+
graph.append(firstResult.graph)
59
59
+
graph.append(secondResult.graph)
60
60
+
61
61
+
switch (firstResult.output, secondResult.output) {
62
62
+
case let (.value(firstOutput), .value(secondOutput)) where !diagnostics.hasErrors:
63
63
+
return .value((firstOutput, secondOutput), graph: graph, diagnostics: diagnostics)
64
64
+
case (.value, .value), (.value, .invalid), (.invalid, .value), (.invalid, .invalid):
65
65
+
return .invalid(graph: graph, diagnostics: diagnostics)
66
66
+
}
67
67
+
}
68
68
+
}
···
100
100
component
101
101
}
102
102
103
103
-
public static func buildBlock<each Component>(_ component: repeat each Component) -> TupleFileSystemComponent<repeat each Component> where repeat each Component: FileTreeReader {
104
104
-
return TupleFileSystemComponent(repeat each component)
105
105
-
}
103
103
+
public static func buildBlock<First, Second>(
104
104
+
_ first: First,
105
105
+
_ second: Second
106
106
+
) -> PairFileSystemComponent<First, Second>
107
107
+
where First: FileTreeReader, Second: FileTreeReader {
108
108
+
PairFileSystemComponent(first, second)
109
109
+
}
110
110
+
111
111
+
public static func buildBlock<each Component>(
112
112
+
_ component: repeat each Component
113
113
+
) -> TupleFileSystemComponent<repeat each Component>
114
114
+
where repeat each Component: FileTreeReader {
115
115
+
TupleFileSystemComponent(repeat each component)
116
116
+
}
106
117
107
118
// public static func buildPartialBlock<F: FileTreeReader>(first content: F) -> F {
108
119
// content
···
85
85
)
86
86
}
87
87
88
88
+
@Test func tupleReadHandlesMissingOptionalConvertedFile() throws {
89
89
+
let directory = try TupleWriteTemporaryDirectory()
90
90
+
try Data("Meadow Stage".utf8)
91
91
+
.write(to: directory.url.appendingPathComponent("venue", withType: "txt"))
92
92
+
93
93
+
let tree = FileTree {
94
94
+
File.Optional("event", "txt")
95
95
+
.convert(TupleWriteTrimmingConversion())
96
96
+
File("venue", "txt")
97
97
+
.convert(TupleWriteTrimmingConversion())
98
98
+
}
99
99
+
100
100
+
let output = try tree.read(from: directory.url).output.requiredValue
101
101
+
102
102
+
#expect(output.0 == nil)
103
103
+
#expect(output.1 == "Meadow Stage")
104
104
+
}
105
105
+
106
106
+
@Test func tupleReadHandlesMissingOptionalConvertedFileBeforeDirectoryMany() throws {
107
107
+
let directory = try TupleWriteTemporaryDirectory()
108
108
+
let eventsURL = directory.url.appending(component: "events")
109
109
+
let eventURL = eventsURL.appending(component: "wicked-woods")
110
110
+
try FileManager.default.createDirectory(at: eventURL, withIntermediateDirectories: true)
111
111
+
try Data("Wicked Woods".utf8)
112
112
+
.write(to: eventURL.appendingPathComponent("event", withType: "txt"))
113
113
+
114
114
+
let tree = FileTree {
115
115
+
File.Optional("calendar", "txt")
116
116
+
.convert(TupleWriteTrimmingConversion())
117
117
+
Directory("events") {
118
118
+
Directory.Many {
119
119
+
File("event", "txt")
120
120
+
.convert(TupleWriteTrimmingConversion())
121
121
+
}
122
122
+
}
123
123
+
}
124
124
+
125
125
+
let output = try tree.read(from: directory.url).output.requiredValue
126
126
+
127
127
+
#expect(output.0 == nil)
128
128
+
#expect(output.1.map(\.directoryName) == ["wicked-woods"])
129
129
+
#expect(output.1.map(\.components) == ["Wicked Woods"])
130
130
+
}
131
131
+
132
132
+
@Test func convertedTupleReadHandlesMissingOptionalConvertedFileBeforeDirectoryMany() throws {
133
133
+
let directory = try TupleWriteTemporaryDirectory()
134
134
+
let eventsURL = directory.url.appending(component: "events")
135
135
+
let eventURL = eventsURL.appending(component: "wicked-woods")
136
136
+
try FileManager.default.createDirectory(at: eventURL, withIntermediateDirectories: true)
137
137
+
try Data("Wicked Woods".utf8)
138
138
+
.write(to: eventURL.appendingPathComponent("event", withType: "txt"))
139
139
+
140
140
+
let tree = FileTree {
141
141
+
File.Optional("calendar", "txt")
142
142
+
.convert(TupleWriteTrimmingConversion())
143
143
+
Directory("events") {
144
144
+
Directory.Many {
145
145
+
File("event", "txt")
146
146
+
.convert(TupleWriteTrimmingConversion())
147
147
+
}
148
148
+
}
149
149
+
}
150
150
+
.convert(TupleWriteRepositoryConversion())
151
151
+
152
152
+
let output = try tree.read(from: directory.url).output.requiredValue
153
153
+
154
154
+
#expect(output.calendar == nil)
155
155
+
#expect(output.events.map(\.directoryName) == ["wicked-woods"])
156
156
+
#expect(output.events.map(\.components) == ["Wicked Woods"])
157
157
+
}
158
158
+
159
159
+
@Test func nestedWriterReadHandlesMissingOptionalConvertedFileBeforeDirectoryMany() throws {
160
160
+
let directory = try TupleWriteTemporaryDirectory()
161
161
+
let eventsURL = directory.url.appending(component: "events")
162
162
+
let eventURL = eventsURL.appending(component: "wicked-woods")
163
163
+
try FileManager.default.createDirectory(at: eventURL, withIntermediateDirectories: true)
164
164
+
try Data("Wicked Woods".utf8)
165
165
+
.write(to: eventURL.appendingPathComponent("event", withType: "txt"))
166
166
+
167
167
+
let output = try TupleWriteRepositoryFileTree()
168
168
+
.read(from: directory.url)
169
169
+
.output
170
170
+
.requiredValue
171
171
+
172
172
+
#expect(output.calendar == nil)
173
173
+
#expect(output.events.map(\.directoryName) == ["wicked-woods"])
174
174
+
#expect(output.events.map(\.components.event) == ["Wicked Woods"])
175
175
+
#expect(output.events.map(\.components.stages) == [nil])
176
176
+
#expect(output.events.map(\.components.participants) == [nil])
177
177
+
}
178
178
+
88
179
@Test func directoryTupleWritePrefixesGraphAndWritesInsideDirectory() throws {
89
180
let directory = try TupleWriteTemporaryDirectory()
90
181
···
329
420
)
330
421
]
331
422
)
423
423
+
)
424
424
+
}
425
425
+
}
426
426
+
427
427
+
private struct TupleWriteRepository: Equatable {
428
428
+
var calendar: String?
429
429
+
var events: [DirectoryContent<String>]
430
430
+
}
431
431
+
432
432
+
private struct TupleWriteRepositoryConversion: Conversion {
433
433
+
func apply(_ input: (String?, [DirectoryContent<String>])) throws -> TupleWriteRepository {
434
434
+
TupleWriteRepository(calendar: input.0, events: input.1)
435
435
+
}
436
436
+
437
437
+
func unapply(_ output: TupleWriteRepository) throws -> (String?, [DirectoryContent<String>]) {
438
438
+
(output.calendar, output.events)
439
439
+
}
440
440
+
}
441
441
+
442
442
+
private struct TupleWriteRepositoryFileTree: FileTreeWriter {
443
443
+
var body: some FileTreeWriter<TupleWriteNestedRepository> {
444
444
+
FileTree {
445
445
+
File.Optional("calendar", "txt")
446
446
+
.convert(TupleWriteCalendarConversion())
447
447
+
448
448
+
Directory("events") {
449
449
+
Directory.Many {
450
450
+
TupleWriteEventFileTree()
451
451
+
}
452
452
+
}
453
453
+
}
454
454
+
.convert(TupleWriteNestedRepositoryConversion())
455
455
+
}
456
456
+
}
457
457
+
458
458
+
private struct TupleWriteEventFileTree: FileTreeWriter {
459
459
+
var body: some FileTreeWriter<TupleWriteSourceEvent> {
460
460
+
FileTree {
461
461
+
File("event", "txt")
462
462
+
.convert(TupleWriteTrimmingConversion())
463
463
+
464
464
+
Directory.Optional("stages") {
465
465
+
File.Many(withExtension: "txt")
466
466
+
.map(FileContentConversion(TupleWriteTrimmingConversion()))
467
467
+
}
468
468
+
469
469
+
Directory.Optional("participants") {
470
470
+
File.Many(withExtension: "txt")
471
471
+
.map(FileContentConversion(TupleWriteTrimmingConversion()))
472
472
+
}
473
473
+
}
474
474
+
.convert(TupleWriteSourceEventConversion())
475
475
+
}
476
476
+
}
477
477
+
478
478
+
private struct TupleWriteNestedRepository: Equatable {
479
479
+
var calendar: TupleWriteCalendar?
480
480
+
var events: [DirectoryContent<TupleWriteSourceEvent>]
481
481
+
}
482
482
+
483
483
+
private struct TupleWriteCalendar: Equatable {
484
484
+
var name: String
485
485
+
var description: String?
486
486
+
}
487
487
+
488
488
+
private struct TupleWriteSourceEvent: Equatable {
489
489
+
var event: String
490
490
+
var stages: [FileContent<String>]?
491
491
+
var participants: [FileContent<String>]?
492
492
+
}
493
493
+
494
494
+
private struct TupleWriteNestedRepositoryConversion: Conversion {
495
495
+
func apply(
496
496
+
_ input: (TupleWriteCalendar?, [DirectoryContent<TupleWriteSourceEvent>])
497
497
+
) throws -> TupleWriteNestedRepository {
498
498
+
TupleWriteNestedRepository(calendar: input.0, events: input.1)
499
499
+
}
500
500
+
501
501
+
func unapply(
502
502
+
_ output: TupleWriteNestedRepository
503
503
+
) throws -> (TupleWriteCalendar?, [DirectoryContent<TupleWriteSourceEvent>]) {
504
504
+
(output.calendar, output.events)
505
505
+
}
506
506
+
}
507
507
+
508
508
+
private struct TupleWriteCalendarConversion: Conversion {
509
509
+
func apply(_ input: Data) throws -> TupleWriteCalendar {
510
510
+
TupleWriteCalendar(
511
511
+
name: String(decoding: input, as: UTF8.self),
512
512
+
description: nil
513
513
+
)
514
514
+
}
515
515
+
516
516
+
func unapply(_ output: TupleWriteCalendar) throws -> Data {
517
517
+
Data(output.name.utf8)
518
518
+
}
519
519
+
}
520
520
+
521
521
+
private struct TupleWriteSourceEventConversion: Conversion {
522
522
+
func apply(
523
523
+
_ input: (
524
524
+
String,
525
525
+
[FileContent<String>]?,
526
526
+
[FileContent<String>]?
527
527
+
)
528
528
+
) throws -> TupleWriteSourceEvent {
529
529
+
TupleWriteSourceEvent(
530
530
+
event: input.0,
531
531
+
stages: input.1,
532
532
+
participants: input.2
533
533
+
)
534
534
+
}
535
535
+
536
536
+
func unapply(
537
537
+
_ output: TupleWriteSourceEvent
538
538
+
) throws -> (
539
539
+
String,
540
540
+
[FileContent<String>]?,
541
541
+
[FileContent<String>]?
542
542
+
) {
543
543
+
(
544
544
+
output.event,
545
545
+
output.stages,
546
546
+
output.participants
332
547
)
333
548
}
334
549
}