Determine required files from custom HTML

This commit is contained in:
Christoph Hagen
2025-07-12 09:22:33 +02:00
parent ba6097a67b
commit 43b761b593
5 changed files with 53 additions and 7 deletions

View File

@@ -156,15 +156,18 @@ struct HtmlCommand: CommandProcessor {
return return
} }
if findFile(withAbsolutePath: path) { if findFile(withAbsolutePath: path) {
// File marked as required
return return
} }
let fileId = path.dropBeforeLast("/") results.requiredOutput(path.withLeadingSlashRemoved, source: "HTML: \(source)")
if content.isValidIdForFile(fileId) {
results.missing(file: fileId, source: "HTML: \(source)") // let fileId = path.dropBeforeLast("/")
} else { // if content.isValidIdForFile(fileId) {
results.warning("Could not find file '\(path)' for \(source)") // results.missing(file: fileId, source: "HTML: \(source)")
} // } else {
// results.warning("Could not find file '\(path)' for \(source)")
// }
} }
private func findFile(withAbsolutePath absolutePath: String) -> Bool { private func findFile(withAbsolutePath absolutePath: String) -> Bool {

View File

@@ -49,12 +49,17 @@ final class GenerationResults: ObservableObject {
@Published @Published
var emptyPages: Set<LocalizedPageId> = [] var emptyPages: Set<LocalizedPageId> = []
/// The paths to the files in the output folder, without leading slashes
@Published @Published
var outputFiles: Set<String> = [] var outputFiles: Set<String> = []
@Published @Published
var unusedFilesInOutput: Set<String> = [] var unusedFilesInOutput: Set<String> = []
/// The paths to files required to be in the output folder, without leading slashes
@Published
var requiredOutputFiles: Set<String> = []
/** /**
The url redirects to install to prevent broken links. The url redirects to install to prevent broken links.
@@ -126,6 +131,7 @@ final class GenerationResults: ObservableObject {
self.redirects = [:] self.redirects = [:]
self.outputFiles = [] self.outputFiles = []
self.unusedFilesInOutput = [] self.unusedFilesInOutput = []
self.requiredOutputFiles = []
} }
for result in cache.values { for result in cache.values {
result.reset() result.reset()
@@ -257,10 +263,26 @@ final class GenerationResults: ObservableObject {
} }
func determineFiles(unusedIn existingFiles: Set<String>) { func determineFiles(unusedIn existingFiles: Set<String>) {
let unused = existingFiles.subtracting(outputFiles) // All paths with leading without leading slashes
let unused = existingFiles.subtracting(outputFiles).subtracting(requiredOutputFiles)
update { self.unusedFilesInOutput = unused } update { self.unusedFilesInOutput = unused }
} }
func determineMissingRequiredFiles(existingFiles: Set<String>) {
// All paths with leading without leading slashes
// Check the files required in the output against the existing files,
// and flag missing ones
let externalFilePaths = self.externalFiles.map { $0.absoluteUrl.withLeadingSlashRemoved }
let fullFiles = existingFiles.union(externalFilePaths)
let missing = requiredOutputFiles.filter { path in
!fullFiles.contains(path) &&
!fullFiles.contains(path + ".html") &&
!fullFiles.contains(path + "/1.html")
}
update { self.requiredOutputFiles = missing }
}
func sources(forMissingPage page: String) -> [(page: LocalizedItemId, source: String)] { func sources(forMissingPage page: String) -> [(page: LocalizedItemId, source: String)] {
var all = [(page: LocalizedItemId, source: String)]() var all = [(page: LocalizedItemId, source: String)]()
for (id, results) in cache { for (id, results) in cache {
@@ -272,6 +294,10 @@ final class GenerationResults: ObservableObject {
} }
return all return all
} }
func requiredOutputFile(_ path: String) {
update { self.requiredOutputFiles.insert(path) }
}
} }
private extension Dictionary where Value == Set<LocalizedItemId> { private extension Dictionary where Value == Set<LocalizedItemId> {

View File

@@ -94,6 +94,10 @@ final class PageGenerationResults: ObservableObject {
@Published @Published
private(set) var unsavedOutputFiles: [String: Set<ItemReference>] = [:] private(set) var unsavedOutputFiles: [String: Set<ItemReference>] = [:]
/// The files that need to be present in the output folder
@Published
private(set) var requiredOutputFiles: [String: Set<String>] = [:]
private(set) var pageIsEmpty: Bool private(set) var pageIsEmpty: Bool
private(set) var redirect: (originalUrl: String, newUrl: String)? private(set) var redirect: (originalUrl: String, newUrl: String)?
@@ -120,6 +124,7 @@ final class PageGenerationResults: ObservableObject {
invalidBlocks = [] invalidBlocks = []
warnings = [] warnings = []
unsavedOutputFiles = [:] unsavedOutputFiles = [:]
requiredOutputFiles = [:]
pageIsEmpty = false pageIsEmpty = false
redirect = nil redirect = nil
} }
@@ -151,6 +156,7 @@ final class PageGenerationResults: ObservableObject {
self.invalidBlocks = [] self.invalidBlocks = []
self.warnings = [] self.warnings = []
self.unsavedOutputFiles = [:] self.unsavedOutputFiles = [:]
self.requiredOutputFiles = [:]
self.pageIsEmpty = false self.pageIsEmpty = false
self.redirect = nil self.redirect = nil
} }
@@ -258,6 +264,11 @@ final class PageGenerationResults: ObservableObject {
onMain { self.requiredIcons.formUnion(icons) } onMain { self.requiredIcons.formUnion(icons) }
} }
func requiredOutput(_ path: String, source: String) {
onMain { self.requiredOutputFiles[path, default: []].insert(source) }
delegate.requiredOutputFile(path)
}
func linked(to page: Page) { func linked(to page: Page) {
onMain { self.linkedPages.insert(page) } onMain { self.linkedPages.insert(page) }
} }

View File

@@ -387,7 +387,9 @@ extension Content {
private func updateUnusedFiles() { private func updateUnusedFiles() {
let existing = storage.getAllOutputFiles() let existing = storage.getAllOutputFiles()
DispatchQueue.main.async { DispatchQueue.main.async {
self.results.determineMissingRequiredFiles(existingFiles: existing)
self.results.determineFiles(unusedIn: existing) self.results.determineFiles(unusedIn: existing)
self.results.objectWillChange.send()
} }
} }
} }

View File

@@ -115,6 +115,10 @@ struct GenerationContentView: View {
Button("Delete", action: { delete(unusedFile: filePath) }) Button("Delete", action: { delete(unusedFile: filePath) })
} }
} }
GenerationStringIssuesView(
text: "missing output files",
statusWhenNonEmpty: .warning,
items: content.results.requiredOutputFiles)
GenerationStringIssuesView( GenerationStringIssuesView(
text: "inaccessible files", text: "inaccessible files",
items: content.results.inaccessibleFiles) { $0.identifier } items: content.results.inaccessibleFiles) { $0.identifier }