Write all logs to disk
This commit is contained in:
parent
956cfb52c4
commit
a8b328efce
@ -3,7 +3,7 @@ import Foundation
|
||||
struct FileData {
|
||||
|
||||
///The files marked as expected, i.e. they exist after the generation is completed. (`key`: file path, `value`: the file providing the link)
|
||||
var expected: [String : String] = [:]
|
||||
var expected: [String : String] = [:]
|
||||
|
||||
/// All files which should be copied to the output folder (`key`: The file path, `value`: The source requiring the file)
|
||||
var toCopy: [String : String] = [:]
|
||||
|
@ -157,9 +157,10 @@ final class FileGenerator {
|
||||
command = "cleancss \(url.path) -o \(tempFile.path)"
|
||||
}
|
||||
|
||||
try? tempFile.delete()
|
||||
|
||||
defer { didMinifyFile() }
|
||||
defer {
|
||||
didMinifyFile()
|
||||
try? tempFile.delete()
|
||||
}
|
||||
|
||||
let output: String
|
||||
do {
|
||||
@ -256,4 +257,30 @@ final class FileGenerator {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func writeResults(to file: URL) {
|
||||
var lines: [String] = []
|
||||
func add<S>(_ name: String, _ property: S, convert: (S.Element) -> String) where S: Sequence {
|
||||
let elements = property.map { " " + convert($0) }.sorted()
|
||||
guard !elements.isEmpty else {
|
||||
return
|
||||
}
|
||||
lines.append("\(name):")
|
||||
lines.append(contentsOf: elements)
|
||||
}
|
||||
add("Unreadable files", unreadableFiles) { "\($0.key) (required by \($0.value.source)): \($0.value.message)" }
|
||||
add("Unwritable files", unwritableFiles) { "\($0.key) (required by \($0.value.source)): \($0.value.message)" }
|
||||
add("Failed minifications", failedMinifications) { "\($0.file) (required by \($0.source)): \($0.message)" }
|
||||
add("Missing files", missingFiles) { "\($0.key) (required by \($0.value))" }
|
||||
add("Copied files", copiedFiles) { $0 }
|
||||
add("Minified files", minifiedFiles) { $0 }
|
||||
add("Expected files", files.expected) { "\($0.key) (required by \($0.value))" }
|
||||
|
||||
let data = lines.joined(separator: "\n").data(using: .utf8)!
|
||||
do {
|
||||
try data.createFolderAndWrite(to: file)
|
||||
} catch {
|
||||
print(" Failed to save log: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -441,7 +441,7 @@ final class GenerationResultsHandler {
|
||||
print("")
|
||||
}
|
||||
|
||||
func writeResultsToFile(file: URL) throws {
|
||||
func writeResults(to file: URL) {
|
||||
var lines: [String] = []
|
||||
func add<S>(_ name: String, _ property: S, convert: (S.Element) -> String) where S: Sequence {
|
||||
let elements = property.map { " " + convert($0) }.sorted()
|
||||
@ -459,9 +459,12 @@ final class GenerationResultsHandler {
|
||||
add("Drafts", draftPages) { $0 }
|
||||
add("Empty pages", emptyPages) { "\($0.key) (from \($0.value))" }
|
||||
add("Generated files", generatedFiles) { $0 }
|
||||
|
||||
let data = lines.joined(separator: "\n").data(using: .utf8)!
|
||||
try data.createFolderAndWrite(to: file)
|
||||
do {
|
||||
try data.createFolderAndWrite(to: file)
|
||||
} catch {
|
||||
print(" Failed to save log: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,18 +24,24 @@ final class ImageGenerator {
|
||||
/**
|
||||
All warnings produced for images during generation
|
||||
*/
|
||||
private var imageWarnings: Set<String> = []
|
||||
private var imageWarnings: [String]
|
||||
|
||||
/**
|
||||
All images modified or created during this generator run.
|
||||
*/
|
||||
/// The images which could not be found, but are required for the site (`key`: image path, `value`: source element path)
|
||||
private var missingImages: [String : String]
|
||||
|
||||
/// Images which could not be read (`key`: file path relative to content, `value`: source element path)
|
||||
private var unreadableImages: [String : String]
|
||||
|
||||
private var unhandledImages: [String: String] = [:]
|
||||
|
||||
/// All images modified or created during this generator run.
|
||||
private var generatedImages: Set<String> = []
|
||||
|
||||
/**
|
||||
The images optimized by ImageOptim
|
||||
*/
|
||||
/// The images optimized by ImageOptim
|
||||
private var optimizedImages: Set<String> = []
|
||||
|
||||
private var failedImages: [(path: String, message: String)] = []
|
||||
|
||||
private var numberOfGeneratedImages = 0
|
||||
|
||||
private let numberOfTotalImages: Int
|
||||
@ -71,6 +77,9 @@ final class ImageGenerator {
|
||||
self.images = images
|
||||
self.numberOfTotalImages = images.jobs.reduce(0) { $0 + $1.value.count }
|
||||
+ images.multiJobs.count * 2
|
||||
self.imageWarnings = images.warnings
|
||||
self.missingImages = images.missing
|
||||
self.unreadableImages = images.unreadable
|
||||
}
|
||||
|
||||
func generateImages() {
|
||||
@ -81,15 +90,10 @@ final class ImageGenerator {
|
||||
}
|
||||
notes.append("\(count) \(name)")
|
||||
}
|
||||
addIfNotZero(images.missing.count, "missing images")
|
||||
addIfNotZero(images.unreadable.count, "unreadable images")
|
||||
|
||||
print(" Changed sources: \(jobs.count)/\(images.jobs.count)")
|
||||
print(" Total images: \(numberOfTotalImages) (\(numberOfTotalImages - imageReader.numberOfFilesAccessed) versions)")
|
||||
print(" Warnings: \(images.warnings.count)")
|
||||
if !notes.isEmpty {
|
||||
print(" Notes: " + notes.joined(separator: ", "))
|
||||
}
|
||||
|
||||
for (source, jobs) in jobs {
|
||||
create(images: jobs, from: source)
|
||||
}
|
||||
@ -99,11 +103,18 @@ final class ImageGenerator {
|
||||
print(" Generated images: \(numberOfGeneratedImages)/\(numberImagesToCreate)")
|
||||
optimizeImages()
|
||||
print(" Optimized images: \(numberOfOptimizedImages)/\(numberOfImagesToOptimize)")
|
||||
|
||||
addIfNotZero(missingImages.count, "missing images")
|
||||
addIfNotZero(unreadableImages.count, "unreadable images")
|
||||
print(" Warnings: \(imageWarnings.count)")
|
||||
if !notes.isEmpty {
|
||||
print(" Notes: " + notes.joined(separator: ", "))
|
||||
}
|
||||
}
|
||||
|
||||
private func create(images: [ImageJob], from source: String) {
|
||||
guard let image = imageReader.getImage(atPath: source) else {
|
||||
// TODO: Add to failed images
|
||||
unreadableImages[source] = images.first!.destination
|
||||
didGenerateImage(count: images.count)
|
||||
return
|
||||
}
|
||||
@ -137,7 +148,7 @@ final class ImageGenerator {
|
||||
|
||||
let destinationExtension = destinationUrl.pathExtension.lowercased()
|
||||
guard let type = ImageType(fileExtension: destinationExtension)?.fileType else {
|
||||
addWarning("Invalid image extension \(destinationExtension)", job: job)
|
||||
unhandledImages[source] = job.destination
|
||||
return
|
||||
}
|
||||
|
||||
@ -168,25 +179,20 @@ final class ImageGenerator {
|
||||
|
||||
// Get NSData, and save it
|
||||
guard let data = rep.representation(using: type, properties: [.compressionFactor: NSNumber(value: job.quality)]) else {
|
||||
addWarning("Failed to get data", job: job)
|
||||
markImageAsFailed(source, error: "Failed to get data")
|
||||
return
|
||||
}
|
||||
do {
|
||||
try data.createFolderAndWrite(to: destinationUrl)
|
||||
} catch {
|
||||
addWarning("Failed to write image (\(error))", job: job)
|
||||
markImageAsFailed(job.destination, error: "Failed to write image (\(error))")
|
||||
return
|
||||
}
|
||||
generatedImages.insert(job.destination)
|
||||
}
|
||||
|
||||
private func addWarning(_ message: String, destination: String, path: String) {
|
||||
let warning = " \(destination): \(message) required by \(path)"
|
||||
imageWarnings.insert(warning)
|
||||
}
|
||||
|
||||
private func addWarning(_ message: String, job: ImageJob) {
|
||||
addWarning(message, destination: job.destination, path: job.path)
|
||||
private func markImageAsFailed(_ source: String, error: String) {
|
||||
failedImages.append((source, error))
|
||||
}
|
||||
|
||||
private func createMultiImages(from source: String, path: String) {
|
||||
@ -198,7 +204,7 @@ final class ImageGenerator {
|
||||
let sourceUrl = output.appendingPathComponent(source)
|
||||
let sourcePath = sourceUrl.path
|
||||
guard sourceUrl.exists else {
|
||||
addWarning("No image at path \(sourcePath)", destination: source, path: path)
|
||||
missingImages[source] = path
|
||||
didGenerateImage(count: 2)
|
||||
return
|
||||
}
|
||||
@ -222,7 +228,7 @@ final class ImageGenerator {
|
||||
do {
|
||||
_ = try safeShell(command)
|
||||
} catch {
|
||||
addWarning("Failed to create AVIF image", destination: destination, path: destination)
|
||||
markImageAsFailed(destination, error: "Failed to create AVIF image")
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,7 +237,7 @@ final class ImageGenerator {
|
||||
do {
|
||||
_ = try safeShell(command)
|
||||
} catch {
|
||||
addWarning("Failed to create WEBP image", destination: destination, path: destination)
|
||||
markImageAsFailed(destination, error: "Failed to create WEBP image")
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,7 +246,7 @@ final class ImageGenerator {
|
||||
do {
|
||||
_ = try safeShell(command)
|
||||
} catch {
|
||||
addWarning("Failed to compress image", destination: destination, path: destination)
|
||||
markImageAsFailed(destination, error: "Failed to compress image")
|
||||
}
|
||||
}
|
||||
|
||||
@ -264,7 +270,9 @@ final class ImageGenerator {
|
||||
_ = try safeShell(command)
|
||||
return true
|
||||
} catch {
|
||||
addWarning("Failed to optimize images", destination: "", path: "")
|
||||
for image in batch {
|
||||
markImageAsFailed(image, error: "Failed to optimize image")
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -282,4 +290,29 @@ final class ImageGenerator {
|
||||
print(" Optimized images: \(numberOfOptimizedImages)/\(numberOfImagesToOptimize) \r", terminator: "")
|
||||
fflush(stdout)
|
||||
}
|
||||
|
||||
func writeResults(to file: URL) {
|
||||
var lines: [String] = []
|
||||
func add<S>(_ name: String, _ property: S, convert: (S.Element) -> String) where S: Sequence {
|
||||
let elements = property.map { " " + convert($0) }.sorted()
|
||||
guard !elements.isEmpty else {
|
||||
return
|
||||
}
|
||||
lines.append("\(name):")
|
||||
lines.append(contentsOf: elements)
|
||||
}
|
||||
add("Missing images", missingImages) { "\($0.key) (required by \($0.value))" }
|
||||
add("Unreadable images", unreadableImages) { "\($0.key) (required by \($0.value))" }
|
||||
add("Failed images", failedImages) { "\($0.path): \($0.message)" }
|
||||
add("Unhandled images", unhandledImages) { "\($0.value) (from \($0.key))" }
|
||||
add("Warnings", imageWarnings) { $0 }
|
||||
add("Generated images", generatedImages) { $0 }
|
||||
add("Optimized images", optimizedImages) { $0 }
|
||||
let data = lines.joined(separator: "\n").data(using: .utf8)!
|
||||
do {
|
||||
try data.createFolderAndWrite(to: file)
|
||||
} catch {
|
||||
print(" Failed to save log: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ final class MetadataInfoLogger {
|
||||
|
||||
private let input: URL
|
||||
|
||||
private let runFolder: URL
|
||||
|
||||
private var numberOfMetadataFiles = 0
|
||||
|
||||
private var unusedProperties: [(name: String, source: String)] = []
|
||||
@ -22,13 +20,8 @@ final class MetadataInfoLogger {
|
||||
|
||||
private var errors: [(source: String, message: String)] = []
|
||||
|
||||
private var logFile: URL {
|
||||
runFolder.appendingPathComponent("Metadata issues.txt")
|
||||
}
|
||||
|
||||
init(input: URL, runFolder: URL) {
|
||||
init(input: URL) {
|
||||
self.input = input
|
||||
self.runFolder = runFolder
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,7 +124,7 @@ final class MetadataInfoLogger {
|
||||
// MARK: Printing
|
||||
|
||||
private func printMetadataScanUpdate() {
|
||||
print(String(format: " Pages found: %4d \r", numberOfMetadataFiles), terminator: "")
|
||||
print(" Pages found: \(numberOfMetadataFiles) \r", terminator: "")
|
||||
}
|
||||
|
||||
func printMetadataScanOverview(languages: Int) {
|
||||
@ -157,31 +150,29 @@ final class MetadataInfoLogger {
|
||||
}
|
||||
}
|
||||
|
||||
func writeResultsToFile() throws {
|
||||
func writeResults(to file: URL) {
|
||||
var lines: [String] = []
|
||||
if !errors.isEmpty {
|
||||
lines += ["Errors:"] + errors.map { "\($0.source): \($0.message)" }.sorted()
|
||||
}
|
||||
if !warnings.isEmpty {
|
||||
lines += ["Warnings:"] + warnings.map { "\($0.source): \($0.message)" }.sorted()
|
||||
}
|
||||
if !unreadableMetadata.isEmpty {
|
||||
lines += ["Unreadable files:"] + unreadableMetadata.map { "\($0.source): \($0.error)" }.sorted()
|
||||
}
|
||||
if !unusedProperties.isEmpty {
|
||||
lines += ["Unused properties:"] + unusedProperties.map { "\($0.source): \($0.name)" }.sorted()
|
||||
}
|
||||
if !invalidProperties.isEmpty {
|
||||
lines += ["Invalid properties:"] + invalidProperties.map { "\($0.source): \($0.name) (\($0.reason))" }.sorted()
|
||||
}
|
||||
if !unknownProperties.isEmpty {
|
||||
lines += ["Unknown properties:"] + unknownProperties.map { "\($0.source): \($0.name)" }.sorted()
|
||||
}
|
||||
if !missingProperties.isEmpty {
|
||||
lines += ["Missing properties:"] + missingProperties.map { "\($0.source): \($0.name)" }.sorted()
|
||||
func add<S>(_ name: String, _ property: S, convert: (S.Element) -> String) where S: Sequence {
|
||||
let elements = property.map { " " + convert($0) }.sorted()
|
||||
guard !elements.isEmpty else {
|
||||
return
|
||||
}
|
||||
lines.append("\(name):")
|
||||
lines.append(contentsOf: elements)
|
||||
}
|
||||
add("Errors", errors) { "\($0.source): \($0.message)" }
|
||||
add("Warnings", warnings) { "\($0.source): \($0.message)" }
|
||||
add("Unreadable files", unreadableMetadata) { "\($0.source): \($0.error)" }
|
||||
add("Unused properties", unusedProperties) { "\($0.source): \($0.name)" }
|
||||
add("Invalid properties", invalidProperties) { "\($0.source): \($0.name) (\($0.reason))" }
|
||||
add("Unknown properties", unknownProperties) { "\($0.source): \($0.name)" }
|
||||
add("Missing properties", missingProperties) { "\($0.source): \($0.name)" }
|
||||
|
||||
let data = lines.joined(separator: "\n").data(using: .utf8)!
|
||||
try data.createFolderAndWrite(to: logFile)
|
||||
do {
|
||||
try data.createFolderAndWrite(to: file)
|
||||
} catch {
|
||||
print(" Failed to save log: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,13 @@ extension Template {
|
||||
}
|
||||
|
||||
init(from url: URL, results: GenerationResultsHandler) throws {
|
||||
let raw = try String(contentsOf: url)
|
||||
self.init(raw: raw, results: results)
|
||||
do {
|
||||
let raw = try String(contentsOf: url)
|
||||
self.init(raw: raw, results: results)
|
||||
} catch {
|
||||
results.warning("Failed to load: \(error)", source: "Template \(url.lastPathComponent)")
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
|
@ -36,22 +36,29 @@ private func loadSiteData(in folder: URL, runFolder: URL) throws -> (root: Eleme
|
||||
print("--- SOURCE FILES -----------------------------------")
|
||||
print(" ")
|
||||
|
||||
let log = MetadataInfoLogger(input: folder, runFolder: runFolder)
|
||||
let log = MetadataInfoLogger(input: folder)
|
||||
let root = Element(atRoot: folder, log: log)
|
||||
log.printMetadataScanOverview(languages: root?.languages.count ?? 0)
|
||||
print(" ")
|
||||
try log.writeResultsToFile()
|
||||
|
||||
let file = runFolder.appendingPathComponent("metadata.txt")
|
||||
defer {
|
||||
log.writeResults(to: file)
|
||||
print(" ")
|
||||
}
|
||||
guard let root else {
|
||||
log.printMetadataScanOverview(languages: 0)
|
||||
print(" Error: No site root loaded, aborting generation")
|
||||
return nil
|
||||
}
|
||||
let ids = root.getContainedIds(log: log)
|
||||
log.printMetadataScanOverview(languages: root.languages.count)
|
||||
return (root, ids)
|
||||
}
|
||||
|
||||
private func generatePages(from root: Element, configuration: Configuration, fileUpdates: FileUpdateChecker, ids: [String: String], pageCount: Int, runFolder: URL) throws -> (ImageData, FileData) {
|
||||
private func generatePages(from root: Element, configuration: Configuration, fileUpdates: FileUpdateChecker, ids: [String: String], runFolder: URL) -> (ImageData, FileData)? {
|
||||
print("--- GENERATION -------------------------------------")
|
||||
print(" ")
|
||||
|
||||
let pageCount = ids.count * root.languages.count
|
||||
let results = GenerationResultsHandler(
|
||||
in: configuration.contentDirectory,
|
||||
to: configuration.outputDirectory,
|
||||
@ -60,11 +67,22 @@ private func generatePages(from root: Element, configuration: Configuration, fil
|
||||
pagePaths: ids,
|
||||
pageCount: pageCount)
|
||||
|
||||
let siteGenerator = try SiteGenerator(results: results)
|
||||
defer { results.printOverview() }
|
||||
|
||||
let siteGenerator: SiteGenerator
|
||||
do {
|
||||
siteGenerator = try SiteGenerator(results: results)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
siteGenerator.generate(site: root)
|
||||
results.printOverview()
|
||||
let url = runFolder.appendingPathComponent("files.txt")
|
||||
try results.writeResultsToFile(file: url)
|
||||
|
||||
let url = runFolder.appendingPathComponent("pages.txt")
|
||||
results.writeResults(to: url)
|
||||
|
||||
if let error = fileUpdates.writeDetectedFileChanges(to: runFolder) {
|
||||
print(" Hashes not saved: \(error)")
|
||||
}
|
||||
return (results.images, results.files)
|
||||
}
|
||||
|
||||
@ -78,6 +96,8 @@ private func generateImages(_ images: ImageData, configuration: Configuration, r
|
||||
reader: reader, images: images)
|
||||
generator.generateImages()
|
||||
print(" ")
|
||||
let file = runFolder.appendingPathComponent("images.txt")
|
||||
generator.writeResults(to: file)
|
||||
}
|
||||
|
||||
private func copyFiles(files: FileData, configuration: Configuration, runFolder: URL) {
|
||||
@ -131,11 +151,9 @@ private func generate(configPath: String) throws {
|
||||
}
|
||||
|
||||
// 3. Generate pages
|
||||
let pageCount = ids.count * siteRoot.languages.count
|
||||
let (images, files) = try generatePages(from: siteRoot, configuration: configuration, fileUpdates: fileUpdates, ids: ids, pageCount: pageCount, runFolder: runFolder)
|
||||
|
||||
if let error = fileUpdates.writeDetectedFileChanges(to: runFolder) {
|
||||
print(error)
|
||||
guard let (images, files) = generatePages(from: siteRoot, configuration: configuration, fileUpdates: fileUpdates, ids: ids, runFolder: runFolder) else {
|
||||
return
|
||||
}
|
||||
|
||||
// 4. Generate images
|
||||
|
Loading…
Reference in New Issue
Block a user