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" /** A custom id to uniquely identify the element on the site. The id is used for short-hand links to pages, in the form of `![page](page_id)` for thumbnail previews or `[text](page:page_id)` for simple links. If no custom id is set, then the name of the element folder is used. */ let customId: String? /** 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 (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? /** Additional images required by the element. These images are specified as: `source_name destination_name width (height)`. */ let images: Set? /** The path to the thumbnail file. This property is optional, and defaults to ``Element.defaultThumbnailName``. Note: The generator first looks for localized versions of the thumbnail by appending `-[lang]` to the file name, e.g. `customThumb-en.jpg`. If no file is found, then the specified file is tried. */ let thumbnailPath: String? /** 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 the header type to be generated automatically. If this option is set to `none`, then custom header code should be present in the page source files - Note: If not specified, this property defaults to `left`. - Note: Overview pages are always using `center`. */ let headerType: String? /** The localized metadata for each language. */ let languages: [LocalizedMetadata]? } extension GenericMetadata: Codable { private static var knownKeyList: [CodingKeys] { [ .customId, .author, .topBarTitle, .date, .endDate, .state, .sortIndex, .externalFiles, .requiredFiles, .images, .thumbnailPath, .thumbnailStyle, .useManualSorting, .overviewItemCount, .headerType, .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, log: MetadataInfoLogger) { guard let data = log.readPotentialMetadata(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 { log.failedToDecodeMetadata(source: source, error: error) return nil } } } extension GenericMetadata { static var full: GenericMetadata { .init( customId: "", author: "", topBarTitle: "", date: "", endDate: "", state: "", sortIndex: 1, externalFiles: [], requiredFiles: [], images: [], thumbnailPath: "", thumbnailStyle: "", useManualSorting: false, overviewItemCount: 6, headerType: "left", languages: [.full]) } }