@@ -26,6 +26,7 @@ struct MergeAction: Action {
26
26
27
27
try validateThatOutputIsEmpty ( )
28
28
try validateThatArchivesHaveDisjointData ( )
29
+ let supportsStaticHosting = try validateThatAllArchivesOrNoArchivesSupportStaticHosting ( )
29
30
30
31
let targetURL = try Self . createUniqueDirectory ( inside: fileManager. uniqueTemporaryDirectory ( ) , template: firstArchive, fileManager: fileManager)
31
32
defer {
@@ -40,18 +41,19 @@ struct MergeAction: Action {
40
41
}
41
42
var combinedJSONIndex = try JSONDecoder ( ) . decode ( RenderIndex . self, from: jsonIndexData)
42
43
44
+ // Ensure that the destination has a data directory in case the first archive didn't have any pages.
45
+ try ? fileManager. createDirectory ( at: targetURL. appendingPathComponent ( " data " , isDirectory: true ) , withIntermediateDirectories: false , attributes: nil )
46
+
47
+ let directoriesToCopy = [ " data/documentation " , " data/tutorials " , " images " , " videos " , " downloads " ] + ( supportsStaticHosting ? [ " documentation " , " tutorials " ] : [ ] )
43
48
for archive in archives. dropFirst ( ) {
44
- for directoryToCopy in [ " data/documentation " , " data/tutorials " , " documentation " , " tutorials " , " images " , " videos " , " downloads " ] {
49
+ for directoryToCopy in directoriesToCopy {
45
50
let fromDirectory = archive. appendingPathComponent ( directoryToCopy, isDirectory: true )
46
51
let toDirectory = targetURL. appendingPathComponent ( directoryToCopy, isDirectory: true )
47
52
48
- var mkdir_p = false
53
+ // Ensure that the destination directory exist in case the first archive didn't have that kind of pages.
54
+ // This is necessary when merging a reference-only archive with a tutorial-only archive.
55
+ try ? fileManager. createDirectory ( at: toDirectory, withIntermediateDirectories: false , attributes: nil )
49
56
for from in ( try ? fileManager. contentsOfDirectory ( at: fromDirectory, includingPropertiesForKeys: nil , options: . skipsHiddenFiles) ) ?? [ ] {
50
- // Create the full path to the destination directory if necessary, once for each directory to copy
51
- if !mkdir_p {
52
- try fileManager. createDirectory ( at: toDirectory, withIntermediateDirectories: true , attributes: nil )
53
- mkdir_p = true
54
- }
55
57
// Copy each file or subdirectory
56
58
try fileManager. copyItem ( at: from, to: toDirectory. appendingPathComponent ( from. lastPathComponent) )
57
59
}
@@ -75,6 +77,7 @@ struct MergeAction: Action {
75
77
return ActionResult ( didEncounterError: false , outputs: [ outputURL] )
76
78
}
77
79
80
+ /// Validate that the different archives don't have overlapping data.
78
81
private func validateThatArchivesHaveDisjointData( ) throws {
79
82
// Check that the archives don't have overlapping data
80
83
typealias ArchivesByDirectoryName = [ String : [ String : Set < String > ] ]
@@ -131,6 +134,7 @@ struct MergeAction: Action {
131
134
}
132
135
}
133
136
137
+ /// Validate that the output directory is empty.
134
138
private func validateThatOutputIsEmpty( ) throws {
135
139
guard fileManager. directoryExists ( atPath: outputURL. path) else {
136
140
return
@@ -162,4 +166,44 @@ struct MergeAction: Action {
162
166
throw NonEmptyOutputError ( existingContents: existingContents, fileManager: fileManager)
163
167
}
164
168
}
169
+
170
+ /// Validate that either all archives support static hosting or that no archives support static hosting.
171
+ /// - Returns: `true` if all archives support static hosting; `false` otherwise.
172
+ private func validateThatAllArchivesOrNoArchivesSupportStaticHosting( ) throws -> Bool {
173
+ let nonEmptyArchives = archives. filter {
174
+ fileManager. directoryExists ( atPath: $0. appendingPathComponent ( " data " ) . path)
175
+ }
176
+
177
+ let archivesWithStaticHostingSupport = nonEmptyArchives. filter {
178
+ return fileManager. directoryExists ( atPath: $0. appendingPathComponent ( " documentation " ) . path)
179
+ || fileManager. directoryExists ( atPath: $0. appendingPathComponent ( " tutorials " ) . path)
180
+ }
181
+
182
+ guard archivesWithStaticHostingSupport. count == nonEmptyArchives. count // All archives support static hosting
183
+ || archivesWithStaticHostingSupport. count == 0 // No archives support static hosting
184
+ else {
185
+ struct DifferentStaticHostingSupportError : DescribedError {
186
+ var withSupport : Set < String >
187
+ var withoutSupport : Set < String >
188
+
189
+ var errorDescription : String {
190
+ """
191
+ Different static hosting support in different archives.
192
+
193
+ \( withSupport. sorted ( ) . joined ( separator: " , " ) ) support \( withSupport. count == 1 ? " s " : " " ) static hosting \
194
+ but \( withoutSupport. sorted ( ) . joined ( separator: " , " ) ) do \( withoutSupport. count == 1 ? " es " : " " ) n't.
195
+ """
196
+ }
197
+ }
198
+ let allArchiveNames = Set ( nonEmptyArchives. map ( \. lastPathComponent) )
199
+ let archiveNamesWithStaticHostingSupport = Set ( archivesWithStaticHostingSupport. map ( \. lastPathComponent) )
200
+
201
+ throw DifferentStaticHostingSupportError (
202
+ withSupport: archiveNamesWithStaticHostingSupport,
203
+ withoutSupport: allArchiveNames. subtracting ( archiveNamesWithStaticHostingSupport)
204
+ )
205
+ }
206
+
207
+ return !archivesWithStaticHostingSupport. isEmpty
208
+ }
165
209
}
0 commit comments