@@ -31,6 +31,8 @@ import Foundation
3131private struct SampleMetadata : Decodable {
3232 /// The title of the sample.
3333 let title : String
34+ /// The category of the sample.
35+ let category : String
3436 /// The description of the sample.
3537 let description : String
3638 /// The relative paths to the code snippets.
@@ -39,6 +41,8 @@ private struct SampleMetadata: Decodable {
3941 let offlineData : [ String ] ?
4042 /// The tags and relevant APIs of the sample.
4143 let keywords : [ String ]
44+ /// The relevant APIs of the sample.
45+ let relevantApis : [ String ]
4246}
4347
4448extension SampleMetadata {
@@ -55,6 +59,16 @@ extension SampleMetadata {
5559 // E.g., DisplayMapView -> DisplayMap
5660 viewName. dropLast ( 4 )
5761 }
62+
63+ /// The tags of a sample.
64+ /// - Note: See common-samples/wiki/README.metadata.json#keywords.
65+ /// The keywords in the sample metadata combines Tags and Relevant APIs
66+ /// from the README. This is done in Scripts/CI/common.py by appending the
67+ /// relevant APIs to the tags. Therefore, dropping the relevant APIs will
68+ /// give the tags.
69+ var tags : Array < String > . SubSequence {
70+ keywords. dropLast ( relevantApis. count)
71+ }
5872}
5973
6074// MARK: Script Entry
@@ -71,24 +85,35 @@ let templateURL = URL(fileURLWithPath: arguments[2], isDirectory: false)
7185let outputFileURL = URL ( fileURLWithPath: arguments [ 3 ] , isDirectory: false )
7286
7387private let sampleMetadata : [ SampleMetadata ] = {
88+ let decoder = JSONDecoder ( )
89+ // Converts snake-case key "offline_data" to camel-case "offlineData".
90+ decoder. keyDecodingStrategy = . convertFromSnakeCase
91+
92+ let fileManager = FileManager . default
7493 do {
75- // Finds all subdirectories under the root samples directory.
76- let decoder = JSONDecoder ( )
77- // Converts snake-case key "offline_data" to camel-case "offlineData".
78- decoder. keyDecodingStrategy = . convertFromSnakeCase
7994 // Does a shallow traverse of the top level of samples directory.
80- return try FileManager . default . contentsOfDirectory ( at: samplesDirectoryURL, includingPropertiesForKeys: nil , options: [ . skipsHiddenFiles] )
95+ return try fileManager . contentsOfDirectory ( at: samplesDirectoryURL, includingPropertiesForKeys: nil , options: [ . skipsHiddenFiles] )
8196 . filter ( \. hasDirectoryPath)
8297 . compactMap { url in
83- // Try to access the metadata file under a subdirectory.
84- guard let data = try ? Data ( contentsOf: url. appendingPathComponent ( " README.metadata.json " ) ) else {
98+ // Gets the metadata JSON under each subdirectory.
99+ let metadataFile = url. appendingPathComponent ( " README.metadata.json " )
100+ guard fileManager. fileExists ( atPath: metadataFile. path) else {
101+ // Sometimes when Git switches branches, it leaves an empty directory
102+ // that causes the decoder to throw an error. Here we skip the directories
103+ // that don't have the metadata JSON file.
85104 return nil
86105 }
87- return try ? decoder. decode ( SampleMetadata . self, from: data)
106+ do {
107+ let data = try Data ( contentsOf: metadataFile)
108+ return try decoder. decode ( SampleMetadata . self, from: data)
109+ } catch {
110+ print ( " error: ' \( url. lastPathComponent) ' sample couldn’t be decoded. " )
111+ exit ( 1 )
112+ }
88113 }
89114 . sorted { $0. title < $1. title }
90115 } catch {
91- print ( " Error decoding Samples: \( error. localizedDescription) " )
116+ print ( " error: Decoding Samples: \( error. localizedDescription) " )
92117 exit ( 1 )
93118 }
94119} ( )
@@ -100,9 +125,10 @@ private let sampleStructs = sampleMetadata
100125 return """
101126 struct \( sample. structName) : Sample {
102127 var name: String { \" \( sample. title) \" }
128+ var category: String { \" \( sample. category) \" }
103129 var description: String { \" \( sample. description) \" }
104130 var snippets: [String] { \( sample. snippets) }
105- var tags: Set<String> { \( sample. keywords ) }
131+ var tags: Set<String> { \( sample. tags ) }
106132 \( portalItemIDs. isEmpty ? " " : " var hasDependencies: Bool { true } \n " )
107133 func makeBody() -> AnyView { .init( \( sample. viewName) ()) }
108134 }
127153 . replacingOccurrences ( of: " /* structs */" , with: sampleStructs)
128154 try content. write ( to: outputFileURL, atomically: true , encoding: . utf8)
129155} catch {
130- print ( " Error reading or writing template file: \( error. localizedDescription) " )
156+ print ( " error: Reading or writing template file: \( error. localizedDescription) " )
131157 exit ( 1 )
132158}
0 commit comments