Improve relative paths, check missing files
This commit is contained in:
parent
761845311e
commit
268c0e5f39
@ -198,8 +198,8 @@ struct Element {
|
||||
}
|
||||
}
|
||||
// TODO: Propagate external files from the parent if subpath matches?
|
||||
self.externalFiles = metadata.externalFiles ?? []
|
||||
self.requiredFiles = Set((metadata.requiredFiles ?? []).map { path + "/" + $0 })
|
||||
self.externalFiles = Element.rootPaths(for: metadata.externalFiles, path: path)
|
||||
self.requiredFiles = Element.rootPaths(for: metadata.requiredFiles, path: path)
|
||||
self.thumbnailStyle = log.thumbnailStyle(metadata.thumbnailStyle, source: source)
|
||||
self.useManualSorting = metadata.useManualSorting ?? false
|
||||
self.overviewItemCount = metadata.overviewItemCount ?? parent.overviewItemCount
|
||||
@ -268,12 +268,33 @@ extension Element {
|
||||
This function is used to copy required input files and to generate images
|
||||
*/
|
||||
func pathRelativeToRootForContainedInputFile(_ filePath: String) -> String {
|
||||
guard !filePath.hasSuffix("/") && !filePath.hasSuffix("http") else {
|
||||
return filePath
|
||||
Element.relativeToRoot(filePath: filePath, folder: path)
|
||||
}
|
||||
|
||||
/**
|
||||
Create an absolute path (relative to the root directory) for a file contained in the elements folder.
|
||||
|
||||
This function is used to copy required input files and to generate images
|
||||
*/
|
||||
func nonAbsolutePathRelativeToRootForContainedInputFile(_ filePath: String) -> String? {
|
||||
Element.containedFileRelativeToRoot(filePath: filePath, folder: path)
|
||||
}
|
||||
|
||||
static func relativeToRoot(filePath: String, folder path: String) -> String {
|
||||
containedFileRelativeToRoot(filePath: filePath, folder: path) ?? filePath
|
||||
}
|
||||
|
||||
static func containedFileRelativeToRoot(filePath: String, folder path: String) -> String? {
|
||||
if filePath.hasPrefix("/") || filePath.hasPrefix("http") || filePath.hasPrefix("mailto:") {
|
||||
return nil
|
||||
}
|
||||
return "\(path)/\(filePath)"
|
||||
}
|
||||
|
||||
static func rootPaths(for input: Set<String>?, path: String) -> Set<String> {
|
||||
input.unwrapped { Set($0.map { relativeToRoot(filePath: $0, folder: path) }) } ?? []
|
||||
}
|
||||
|
||||
func relativePathToFileWithPath(_ filePath: String) -> String {
|
||||
guard path != "" else {
|
||||
return filePath
|
||||
|
@ -21,11 +21,17 @@ extension String {
|
||||
}
|
||||
|
||||
func dropAfterLast(_ separator: String) -> String {
|
||||
components(separatedBy: separator).dropLast().joined(separator: separator)
|
||||
guard contains(separator) else {
|
||||
return self
|
||||
}
|
||||
return components(separatedBy: separator).dropLast().joined(separator: separator)
|
||||
}
|
||||
|
||||
func dropBeforeFirst(_ separator: String) -> String {
|
||||
components(separatedBy: separator).dropFirst().joined(separator: separator)
|
||||
guard contains(separator) else {
|
||||
return self
|
||||
}
|
||||
return components(separatedBy: separator).dropFirst().joined(separator: separator)
|
||||
}
|
||||
|
||||
func lastComponentAfter(_ separator: String) -> String {
|
||||
|
@ -5,7 +5,6 @@ import AppKit
|
||||
typealias SourceFile = (data: Data, didChange: Bool)
|
||||
typealias SourceTextFile = (content: String, didChange: Bool)
|
||||
|
||||
#warning("Skip external files")
|
||||
final class FileSystem {
|
||||
|
||||
private static let hashesFileName = "hashes.json"
|
||||
@ -39,6 +38,20 @@ final class FileSystem {
|
||||
*/
|
||||
private var requiredFiles: Set<String> = []
|
||||
|
||||
/**
|
||||
The files marked as external in element metadata.
|
||||
|
||||
Files included here are not generated, since they are assumed to be added separately.
|
||||
*/
|
||||
private var externalFiles: Set<String> = []
|
||||
|
||||
/**
|
||||
The files marked as expected, i.e. they exist after the generation is completed.
|
||||
|
||||
The key of the dictionary is the file path, the value is the file providing the link
|
||||
*/
|
||||
private var expectedFiles: [String : String] = [:]
|
||||
|
||||
/**
|
||||
The image creation tasks.
|
||||
|
||||
@ -227,7 +240,6 @@ final class FileSystem {
|
||||
return scaledSize
|
||||
}
|
||||
|
||||
#warning("Implement image functions")
|
||||
func createImages() {
|
||||
for (destination, image) in imageTasks.sorted(by: { $0.key < $1.key }) {
|
||||
createImageIfNeeded(image, for: destination)
|
||||
@ -322,12 +334,34 @@ final class FileSystem {
|
||||
requiredFiles.insert(file)
|
||||
}
|
||||
|
||||
/**
|
||||
Mark a file as explicitly missing.
|
||||
|
||||
This is done for the `externalFiles` entries in metadata,
|
||||
to indicate that these files will be copied to the output folder manually.
|
||||
*/
|
||||
func exclude(file: String) {
|
||||
externalFiles.insert(file)
|
||||
}
|
||||
|
||||
/**
|
||||
Mark a file as expected to be present in the output folder after generation.
|
||||
|
||||
This is done for all links between pages, which only exist after the pages have been generated.
|
||||
*/
|
||||
func expect(file: String, source: String) {
|
||||
expectedFiles[file] = source
|
||||
}
|
||||
|
||||
func copyRequiredFiles() {
|
||||
var missingFiles = [String]()
|
||||
for file in requiredFiles {
|
||||
let sourceUrl = input.appendingPathComponent(file)
|
||||
let cleanPath = cleanRelativeURL(file)
|
||||
let sourceUrl = input.appendingPathComponent(cleanPath)
|
||||
let destinationUrl = output.appendingPathComponent(cleanPath)
|
||||
guard sourceUrl.exists else {
|
||||
missingFiles.append(file)
|
||||
if !externalFiles.contains(file) {
|
||||
log.add(error: "Missing required file", source: cleanPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
let data: Data
|
||||
@ -337,15 +371,40 @@ final class FileSystem {
|
||||
log.add(error: "Failed to read data at \(sourceUrl.path)", source: source, error: error)
|
||||
continue
|
||||
}
|
||||
let destinationUrl = output.appendingPathComponent(file)
|
||||
write(data, to: destinationUrl)
|
||||
writeIfChanged(data, to: destinationUrl)
|
||||
}
|
||||
for (file, source) in expectedFiles {
|
||||
guard !externalFiles.contains(file) else {
|
||||
continue
|
||||
}
|
||||
let cleanPath = cleanRelativeURL(file)
|
||||
let destinationUrl = output.appendingPathComponent(cleanPath)
|
||||
if !destinationUrl.exists {
|
||||
log.add(error: "Missing \(cleanPath)", source: source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func cleanRelativeURL(_ raw: String) -> String {
|
||||
let raw = raw.dropAfterLast("#") // Clean links to page content
|
||||
guard raw.contains("..") else {
|
||||
return raw
|
||||
}
|
||||
var result: [String] = []
|
||||
for component in raw.components(separatedBy: "/") {
|
||||
if component == ".." {
|
||||
_ = result.popLast()
|
||||
} else {
|
||||
result.append(component)
|
||||
}
|
||||
}
|
||||
return result.joined(separator: "/")
|
||||
}
|
||||
|
||||
// MARK: Writing files
|
||||
|
||||
@discardableResult
|
||||
func write(_ data: Data, to url: URL) -> Bool {
|
||||
func writeIfChanged(_ data: Data, to url: URL) -> Bool {
|
||||
// Only write changed files
|
||||
if url.exists, let oldContent = try? Data(contentsOf: url), data == oldContent {
|
||||
return false
|
||||
@ -362,7 +421,7 @@ final class FileSystem {
|
||||
@discardableResult
|
||||
func write(_ string: String, to url: URL) -> Bool {
|
||||
let data = string.data(using: .utf8)!
|
||||
return write(data, to: url)
|
||||
return writeIfChanged(data, to: url)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ struct SiteGenerator {
|
||||
|
||||
func generate(site: Element) throws {
|
||||
site.requiredFiles.forEach(files.require)
|
||||
site.externalFiles.forEach(files.exclude)
|
||||
try site.languages.forEach { metadata in
|
||||
let language = metadata.language
|
||||
let template = try LocalizedSiteTemplate(
|
||||
@ -27,6 +28,7 @@ struct SiteGenerator {
|
||||
elementsToProcess.append(contentsOf: element.elements)
|
||||
|
||||
element.requiredFiles.forEach(files.require)
|
||||
element.externalFiles.forEach(files.exclude)
|
||||
|
||||
if !element.elements.isEmpty {
|
||||
overviewGenerator.generate(
|
||||
|
Loading…
Reference in New Issue
Block a user