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?
|
// TODO: Propagate external files from the parent if subpath matches?
|
||||||
self.externalFiles = metadata.externalFiles ?? []
|
self.externalFiles = Element.rootPaths(for: metadata.externalFiles, path: path)
|
||||||
self.requiredFiles = Set((metadata.requiredFiles ?? []).map { path + "/" + $0 })
|
self.requiredFiles = Element.rootPaths(for: metadata.requiredFiles, path: path)
|
||||||
self.thumbnailStyle = log.thumbnailStyle(metadata.thumbnailStyle, source: source)
|
self.thumbnailStyle = log.thumbnailStyle(metadata.thumbnailStyle, source: source)
|
||||||
self.useManualSorting = metadata.useManualSorting ?? false
|
self.useManualSorting = metadata.useManualSorting ?? false
|
||||||
self.overviewItemCount = metadata.overviewItemCount ?? parent.overviewItemCount
|
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
|
This function is used to copy required input files and to generate images
|
||||||
*/
|
*/
|
||||||
func pathRelativeToRootForContainedInputFile(_ filePath: String) -> String {
|
func pathRelativeToRootForContainedInputFile(_ filePath: String) -> String {
|
||||||
guard !filePath.hasSuffix("/") && !filePath.hasSuffix("http") else {
|
Element.relativeToRoot(filePath: filePath, folder: path)
|
||||||
return filePath
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
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)"
|
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 {
|
func relativePathToFileWithPath(_ filePath: String) -> String {
|
||||||
guard path != "" else {
|
guard path != "" else {
|
||||||
return filePath
|
return filePath
|
||||||
|
@ -21,11 +21,17 @@ extension String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dropAfterLast(_ separator: String) -> 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 {
|
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 {
|
func lastComponentAfter(_ separator: String) -> String {
|
||||||
|
@ -5,7 +5,6 @@ import AppKit
|
|||||||
typealias SourceFile = (data: Data, didChange: Bool)
|
typealias SourceFile = (data: Data, didChange: Bool)
|
||||||
typealias SourceTextFile = (content: String, didChange: Bool)
|
typealias SourceTextFile = (content: String, didChange: Bool)
|
||||||
|
|
||||||
#warning("Skip external files")
|
|
||||||
final class FileSystem {
|
final class FileSystem {
|
||||||
|
|
||||||
private static let hashesFileName = "hashes.json"
|
private static let hashesFileName = "hashes.json"
|
||||||
@ -39,6 +38,20 @@ final class FileSystem {
|
|||||||
*/
|
*/
|
||||||
private var requiredFiles: Set<String> = []
|
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.
|
The image creation tasks.
|
||||||
|
|
||||||
@ -227,7 +240,6 @@ final class FileSystem {
|
|||||||
return scaledSize
|
return scaledSize
|
||||||
}
|
}
|
||||||
|
|
||||||
#warning("Implement image functions")
|
|
||||||
func createImages() {
|
func createImages() {
|
||||||
for (destination, image) in imageTasks.sorted(by: { $0.key < $1.key }) {
|
for (destination, image) in imageTasks.sorted(by: { $0.key < $1.key }) {
|
||||||
createImageIfNeeded(image, for: destination)
|
createImageIfNeeded(image, for: destination)
|
||||||
@ -322,12 +334,34 @@ final class FileSystem {
|
|||||||
requiredFiles.insert(file)
|
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() {
|
func copyRequiredFiles() {
|
||||||
var missingFiles = [String]()
|
|
||||||
for file in requiredFiles {
|
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 {
|
guard sourceUrl.exists else {
|
||||||
missingFiles.append(file)
|
if !externalFiles.contains(file) {
|
||||||
|
log.add(error: "Missing required file", source: cleanPath)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let data: Data
|
let data: Data
|
||||||
@ -337,15 +371,40 @@ final class FileSystem {
|
|||||||
log.add(error: "Failed to read data at \(sourceUrl.path)", source: source, error: error)
|
log.add(error: "Failed to read data at \(sourceUrl.path)", source: source, error: error)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let destinationUrl = output.appendingPathComponent(file)
|
writeIfChanged(data, to: destinationUrl)
|
||||||
write(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
|
// MARK: Writing files
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func write(_ data: Data, to url: URL) -> Bool {
|
func writeIfChanged(_ data: Data, to url: URL) -> Bool {
|
||||||
// Only write changed files
|
// Only write changed files
|
||||||
if url.exists, let oldContent = try? Data(contentsOf: url), data == oldContent {
|
if url.exists, let oldContent = try? Data(contentsOf: url), data == oldContent {
|
||||||
return false
|
return false
|
||||||
@ -362,7 +421,7 @@ final class FileSystem {
|
|||||||
@discardableResult
|
@discardableResult
|
||||||
func write(_ string: String, to url: URL) -> Bool {
|
func write(_ string: String, to url: URL) -> Bool {
|
||||||
let data = string.data(using: .utf8)!
|
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 {
|
func generate(site: Element) throws {
|
||||||
site.requiredFiles.forEach(files.require)
|
site.requiredFiles.forEach(files.require)
|
||||||
|
site.externalFiles.forEach(files.exclude)
|
||||||
try site.languages.forEach { metadata in
|
try site.languages.forEach { metadata in
|
||||||
let language = metadata.language
|
let language = metadata.language
|
||||||
let template = try LocalizedSiteTemplate(
|
let template = try LocalizedSiteTemplate(
|
||||||
@ -27,6 +28,7 @@ struct SiteGenerator {
|
|||||||
elementsToProcess.append(contentsOf: element.elements)
|
elementsToProcess.append(contentsOf: element.elements)
|
||||||
|
|
||||||
element.requiredFiles.forEach(files.require)
|
element.requiredFiles.forEach(files.require)
|
||||||
|
element.externalFiles.forEach(files.exclude)
|
||||||
|
|
||||||
if !element.elements.isEmpty {
|
if !element.elements.isEmpty {
|
||||||
overviewGenerator.generate(
|
overviewGenerator.generate(
|
||||||
|
Loading…
Reference in New Issue
Block a user