2022-12-01 14:50:26 +01:00
|
|
|
import Foundation
|
|
|
|
|
|
|
|
final class MetadataInfoLogger {
|
|
|
|
|
|
|
|
private let input: URL
|
|
|
|
|
|
|
|
private var numberOfMetadataFiles = 0
|
|
|
|
|
|
|
|
private var unusedProperties: [(name: String, source: String)] = []
|
|
|
|
|
|
|
|
private var invalidProperties: [(name: String, source: String, reason: String)] = []
|
|
|
|
|
|
|
|
private var unknownProperties: [(name: String, source: String)] = []
|
|
|
|
|
|
|
|
private var missingProperties: [(name: String, source: String)] = []
|
|
|
|
|
|
|
|
private var unreadableMetadata: [(source: String, error: Error)] = []
|
|
|
|
|
|
|
|
private var warnings: [(source: String, message: String)] = []
|
|
|
|
|
|
|
|
private var errors: [(source: String, message: String)] = []
|
|
|
|
|
2022-12-04 23:10:44 +01:00
|
|
|
init(input: URL) {
|
2022-12-01 14:50:26 +01:00
|
|
|
self.input = input
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Adds an info message if a value is set for an unused property.
|
|
|
|
- Note: Unused properties do not cause an element to be skipped.
|
|
|
|
*/
|
|
|
|
@discardableResult
|
|
|
|
func unused<T>(_ value: Optional<T>, _ name: String, source: String) -> T where T: DefaultValueProvider {
|
|
|
|
if let value {
|
|
|
|
unusedProperties.append((name, source))
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
return T.defaultValue
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Cast a string value to another value, and using a default in case of errors.
|
|
|
|
- Note: Invalid string values do not cause an element to be skipped.
|
|
|
|
*/
|
|
|
|
func cast<T>(_ value: String, _ name: String, source: String) -> T where T: DefaultValueProvider, T: StringProperty {
|
|
|
|
guard let result = T.init(value) else {
|
|
|
|
invalidProperties.append((name: name, source: source, reason: T.castFailureReason))
|
|
|
|
return T.defaultValue
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Cast a string value to another value, and using a default in case of errors or missing values.
|
|
|
|
- Note: Invalid string values do not cause an element to be skipped.
|
|
|
|
*/
|
|
|
|
func cast<T>(_ value: String?, _ name: String, source: String) -> T where T: DefaultValueProvider, T: StringProperty {
|
|
|
|
guard let value else {
|
|
|
|
return T.defaultValue
|
|
|
|
}
|
|
|
|
guard let result = T.init(value) else {
|
|
|
|
invalidProperties.append((name: name, source: source, reason: T.castFailureReason))
|
|
|
|
return T.defaultValue
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Cast the string value of an unused property to another value, and using a default in case of errors.
|
|
|
|
- Note: Invalid string values do not cause an element to be skipped.
|
|
|
|
*/
|
|
|
|
func castUnused<R>(_ value: String?, _ name: String, source: String) -> R where R: DefaultValueProvider, R: StringProperty {
|
|
|
|
unused(value.unwrapped { cast($0, name, source: source) }, name, source: source)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Note an unknown property.
|
|
|
|
- Note: Unknown properties do not cause an element to be skipped.
|
|
|
|
*/
|
|
|
|
func unknown(property: String, source: String) {
|
|
|
|
unknownProperties.append((name: property, source: source))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Ensure that a property is set, and aborting metadata decoding.
|
|
|
|
- Note: Missing required properties cause an element to be skipped.
|
|
|
|
*/
|
|
|
|
func required<T>(_ value: T?, name: String, source: String, _ valid: inout Bool) -> T where T: DefaultValueProvider {
|
|
|
|
guard let value = value else {
|
|
|
|
missingProperties.append((name, source))
|
|
|
|
valid = false
|
|
|
|
return T.defaultValue
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
|
|
|
func warning(_ message: String, source: String) {
|
|
|
|
warnings.append((source, message))
|
|
|
|
}
|
|
|
|
|
|
|
|
func error(_ message: String, source: String) {
|
|
|
|
errors.append((source, message))
|
|
|
|
}
|
|
|
|
|
|
|
|
func failedToDecodeMetadata(source: String, error: Error) {
|
|
|
|
unreadableMetadata.append((source, error))
|
|
|
|
}
|
|
|
|
|
|
|
|
func readPotentialMetadata(atPath path: String, source: String) -> Data? {
|
|
|
|
let url = input.appendingPathComponent(path)
|
|
|
|
guard url.exists else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
numberOfMetadataFiles += 1
|
|
|
|
printMetadataScanUpdate()
|
|
|
|
do {
|
|
|
|
return try Data(contentsOf: url)
|
|
|
|
} catch {
|
|
|
|
unreadableMetadata.append((source, error))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: Printing
|
|
|
|
|
|
|
|
private func printMetadataScanUpdate() {
|
2022-12-04 23:10:44 +01:00
|
|
|
print(" Pages found: \(numberOfMetadataFiles) \r", terminator: "")
|
2022-12-01 14:50:26 +01:00
|
|
|
}
|
|
|
|
|
2022-12-04 19:15:22 +01:00
|
|
|
func printMetadataScanOverview(languages: Int) {
|
2022-12-01 15:03:29 +01:00
|
|
|
var notes = [String]()
|
|
|
|
func addIfNotZero<S>(_ sequence: Array<S>, _ name: String) {
|
|
|
|
guard sequence.count > 0 else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
notes.append("\(sequence.count) \(name)")
|
|
|
|
}
|
|
|
|
addIfNotZero(warnings, "warnings")
|
|
|
|
addIfNotZero(errors, "errors")
|
|
|
|
addIfNotZero(unreadableMetadata, "unreadable files")
|
|
|
|
addIfNotZero(unusedProperties, "unused properties")
|
2022-12-04 19:15:22 +01:00
|
|
|
addIfNotZero(invalidProperties, "invalid properties")
|
|
|
|
addIfNotZero(unknownProperties, "unknown properties")
|
|
|
|
addIfNotZero(missingProperties, "missing properties")
|
|
|
|
|
|
|
|
print(" Pages found: \(numberOfMetadataFiles) ")
|
|
|
|
print(" Languages: \(languages)")
|
|
|
|
if !notes.isEmpty {
|
|
|
|
print(" Notes: " + notes.joined(separator: ", "))
|
|
|
|
}
|
2022-12-01 14:50:26 +01:00
|
|
|
}
|
|
|
|
|
2022-12-04 23:10:44 +01:00
|
|
|
func writeResults(to file: URL) {
|
2022-12-01 15:03:29 +01:00
|
|
|
var lines: [String] = []
|
2022-12-04 23:10:44 +01:00
|
|
|
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)
|
2022-12-01 15:03:29 +01:00
|
|
|
}
|
2022-12-04 23:10:44 +01:00
|
|
|
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)" }
|
2022-12-01 14:50:26 +01:00
|
|
|
|
2022-12-04 19:15:22 +01:00
|
|
|
let data = lines.joined(separator: "\n").data(using: .utf8)!
|
2022-12-04 23:10:44 +01:00
|
|
|
do {
|
|
|
|
try data.createFolderAndWrite(to: file)
|
|
|
|
} catch {
|
|
|
|
print(" Failed to save log: \(error)")
|
|
|
|
}
|
2022-12-01 14:50:26 +01:00
|
|
|
}
|
|
|
|
}
|