import Foundation /** The metadata for all site elements. */ struct GenericMetadata { /** The name of the metadata file contained in the folder of each site element. */ static let metadataFileName = "metadata.json" /** The author of the content. If no author is set, then the author from the parent element is used. */ let author: String? /** The title used in the top bar of the website, next to the logo. This title can be HTML content, and only the root level value is used. */ let topBarTitle: String? /** The url where the site will be deployed. This value is required to build absolute links for link previews. - Note: Only the root level value is used. - Note: The path does not need to contain a trailing slash. */ let deployedBaseUrl: String? /** The (start) date of the element. The date is printed on content pages and may also used for sorting elements, depending on the `useManualSorting` property of the parent. */ let date: String? /** The end date of the element. This property can be used to specify a date range for a content page. */ let endDate: String? /** The deployment state of the page. - Note: This property defaults to ``PageState.standard` */ let state: String? /** The sort index of the page for manual sorting. - Note: This property is only used (and must be set) if `useManualSorting` option of the parent is set. */ let sortIndex: Int? /** All files which may occur in content but is stored externally. Missing files which would otherwise produce a warning are ignored when included here. - Note: This property defaults to an empty set. */ let externalFiles: Set? /** Specifies additional files which should be copied to the destination when generating the content. - Note: This property defaults to an empty set. */ let requiredFiles: Set? /** The style of thumbnail to use when generating overviews. - Note: This property is only relevant for sections. - Note: This property is inherited from the parent if not specified. */ let thumbnailStyle: String? /** Sort the child elements by their `sortIndex` property when generating overviews, instead of using the `date`. - Note: This property is only relevant for sections. - Note: This property defaults to `false` */ let useManualSorting: Bool? /** The number of items to show when generating overviews of this element. - Note: This property is only relevant for sections. - Note: This property is inherited from the parent if not specified. */ let overviewItemCount: Int? /** Indicate that no header should be generated automatically. This option assumes that custom header code is present in the page source files - Note: If not specified, this property defaults to `false`. */ let useCustomHeader: Bool? /** The localized metadata for each language. */ let languages: [LocalizedMetadata]? } extension GenericMetadata: Codable { private static var knownKeyList: [CodingKeys] { [ .author, .topBarTitle, .deployedBaseUrl, .date, .endDate, .state, .sortIndex, .externalFiles, .requiredFiles, .thumbnailStyle, .useManualSorting, .overviewItemCount, .useCustomHeader, .languages, ] } static var knownKeys: Set { Set(knownKeyList.map { $0.stringValue }) } } extension GenericMetadata { /** Decode metadata in a folder. - Parameter data: The binary data of the metadata file. - Parameter source: The path to the metadata file, relative to the source root - Note: The decoding routine also checks for unknown properties, and writes them to the output. - Note: Uses global objects */ init?(source: String) { guard let data = files.dataOfOptionalFile(atPath: source, source: source) else { return nil } let decoder = JSONDecoder() let knownKeys = GenericMetadata.knownKeys let knownLocalizedKeys = LocalizedMetadata.knownKeys decoder.keyDecodingStrategy = .custom { keys in let key = keys.last! // Only one key means we are decoding the generic metadata guard keys.count > 1 else { if !knownKeys.contains(key.stringValue) { log.unknown(property: key.stringValue, source: source) } return key } // Two levels means we're decoding the localized metadata if !knownLocalizedKeys.contains(key.stringValue) { log.unknown(property: key.stringValue, source: source) } return key } do { self = try decoder.decode(from: data) } catch { print("Here \(data)") log.failedToOpen(GenericMetadata.metadataFileName, requiredBy: source, error: error) return nil } } }