@@ -62,6 +62,7 @@ public void Execute(GeneratorExecutionContext context)
6262 using var document = JsonDocument . Parse ( json ) ;
6363
6464 StringBuilder defineDictionaryBuilder = new ( ) ;
65+ StringBuilder metadataBuilder = new ( ) ;
6566 StringBuilder propertyBuilder = new ( ) ;
6667 Dictionary < string , string > types = new ( StringComparer . OrdinalIgnoreCase ) ;
6768
@@ -79,6 +80,20 @@ public void Execute(GeneratorExecutionContext context)
7980 types [ ParseKey ( extension ) ] = mimeValue ;
8081 }
8182
83+ var metadataPath = GetMetadataPath ( mimeTypesPath , context ) ;
84+ if ( File . Exists ( metadataPath ) )
85+ {
86+ using var metadataDocument = JsonDocument . Parse ( File . ReadAllBytes ( metadataPath ) ) ;
87+ foreach ( var item in metadataDocument . RootElement . EnumerateObject ( ) )
88+ {
89+ var initializer = BuildMimeTypeInfoInitializer ( item . Name , item . Value ) ;
90+ if ( initializer . Length > 0 )
91+ {
92+ metadataBuilder . AppendLine ( $ "RegisterMimeTypeInfoInternal({ initializer } );") ;
93+ }
94+ }
95+ }
96+
8297 foreach ( var item in types )
8398 {
8499 propertyBuilder . AppendLine ( $ "public static string { item . Key } => \" { Escape ( item . Value ) } \" ;") ;
@@ -87,13 +102,17 @@ public void Execute(GeneratorExecutionContext context)
87102 context . ReportDiagnostic ( Diagnostic . Create ( MimeTypesLoadedDiagnostic , Location . None , types . Count ) ) ;
88103
89104 context . AddSource ( "MimeHelper.Properties.cs" , SourceText . From ( @$ "
105+ using System;
106+ using System.Collections.Immutable;
107+
90108namespace ManagedCode.MimeTypes
91109{{
92110public static partial class MimeHelper
93111{{
94112static partial void Init()
95113{{
96114{ defineDictionaryBuilder }
115+ { metadataBuilder }
97116}}
98117{ propertyBuilder }
99118}}
@@ -117,6 +136,13 @@ static partial void Init()
117136
118137 private string GetMimeTypesPath ( GeneratorExecutionContext context )
119138 {
139+ var additionalFile = context . AdditionalFiles . FirstOrDefault ( static file =>
140+ string . Equals ( Path . GetFileName ( file . Path ) , "mimeTypes.json" , StringComparison . OrdinalIgnoreCase ) ) ;
141+ if ( additionalFile != null )
142+ {
143+ return additionalFile . Path ;
144+ }
145+
120146 // Try to find mimeTypes.json in the project directory
121147 var compilation = context . Compilation ;
122148 var projectDir = Path . GetDirectoryName ( compilation . SyntaxTrees . First ( ) . FilePath ) ;
@@ -136,6 +162,200 @@ private string GetMimeTypesPath(GeneratorExecutionContext context)
136162 return possiblePaths . FirstOrDefault ( File . Exists ) ?? possiblePaths [ 0 ] ;
137163 }
138164
165+ private string GetMetadataPath ( string mimeTypesPath , GeneratorExecutionContext context )
166+ {
167+ var additionalFile = context . AdditionalFiles . FirstOrDefault ( static file =>
168+ string . Equals ( Path . GetFileName ( file . Path ) , "mimeTypes.metadata.json" , StringComparison . OrdinalIgnoreCase ) ) ;
169+ if ( additionalFile != null )
170+ {
171+ return additionalFile . Path ;
172+ }
173+
174+ var mimeTypesDirectory = Path . GetDirectoryName ( mimeTypesPath ) ;
175+ var compilation = context . Compilation ;
176+ var projectDir = Path . GetDirectoryName ( compilation . SyntaxTrees . First ( ) . FilePath ) ;
177+
178+ var possiblePaths = new [ ]
179+ {
180+ Path . Combine ( mimeTypesDirectory ?? "" , "mimeTypes.metadata.json" ) ,
181+ Path . Combine ( Directory . GetCurrentDirectory ( ) , "mimeTypes.metadata.json" ) ,
182+ Path . Combine ( projectDir ?? "" , "mimeTypes.metadata.json" ) ,
183+ Path . Combine ( Directory . GetParent ( projectDir ?? "" ) ? . FullName ?? "" , "mimeTypes.metadata.json" ) ,
184+ Path . Combine ( AppDomain . CurrentDomain . BaseDirectory , "mimeTypes.metadata.json" )
185+ } ;
186+
187+ return possiblePaths . FirstOrDefault ( File . Exists ) ?? possiblePaths [ 0 ] ;
188+ }
189+
190+ private static string BuildMimeTypeInfoInitializer ( string fallbackMime , JsonElement element )
191+ {
192+ if ( element . ValueKind != JsonValueKind . Object )
193+ {
194+ return string . Empty ;
195+ }
196+
197+ var mime = GetString ( element , "mime" ) ?? fallbackMime ;
198+ if ( string . IsNullOrWhiteSpace ( mime ) )
199+ {
200+ return string . Empty ;
201+ }
202+
203+ var builder = new StringBuilder ( ) ;
204+ builder . AppendLine ( "new MimeTypeInfo" ) ;
205+ builder . AppendLine ( "{" ) ;
206+ builder . AppendLine ( $ "Mime = { Literal ( mime ) } ,") ;
207+ builder . AppendLine ( $ "Extensions = { StringArray ( GetStringArray ( element , "extensions" ) ) } ,") ;
208+ builder . AppendLine ( $ "IsIanaRegistered = { BoolLiteral ( GetBool ( element , "isIanaRegistered" ) ) } ,") ;
209+ builder . AppendLine ( $ "IsObsolete = { BoolLiteral ( GetBool ( element , "isObsolete" ) ) } ,") ;
210+ AppendNullableString ( builder , "PreferredMime" , GetString ( element , "preferredMime" ) ) ;
211+ AppendNullableString ( builder , "Template" , GetString ( element , "template" ) ) ;
212+ AppendNullableString ( builder , "TemplateUrl" , GetString ( element , "templateUrl" ) ) ;
213+ AppendNullableString ( builder , "Source" , GetString ( element , "source" ) ) ;
214+ AppendNullableString ( builder , "Registered" , GetString ( element , "registered" ) ) ;
215+ AppendNullableString ( builder , "Updated" , GetString ( element , "updated" ) ) ;
216+ AppendNullableString ( builder , "IntendedUsage" , GetString ( element , "intendedUsage" ) ) ;
217+ AppendNullableString ( builder , "EncodingConsiderations" , GetString ( element , "encodingConsiderations" ) ) ;
218+ AppendNullableString ( builder , "PublishedSpecification" , GetString ( element , "publishedSpecification" ) ) ;
219+ AppendNullableString ( builder , "Applications" , GetString ( element , "applications" ) ) ;
220+ builder . AppendLine ( $ "DeprecatedAliases = { StringArray ( GetStringArray ( element , "deprecatedAliases" ) ) } ,") ;
221+ builder . AppendLine ( $ "References = { StringArray ( GetStringArray ( element , "references" ) ) } ,") ;
222+ builder . AppendLine ( $ "MagicSignatures = { MagicSignatureArray ( element ) } ,") ;
223+ builder . Append ( '}' ) ;
224+ return builder . ToString ( ) ;
225+ }
226+
227+ private static void AppendNullableString ( StringBuilder builder , string propertyName , string ? value )
228+ {
229+ if ( value == null )
230+ {
231+ return ;
232+ }
233+
234+ builder . AppendLine ( $ "{ propertyName } = { Literal ( value ) } ,") ;
235+ }
236+
237+ private static string MagicSignatureArray ( JsonElement element )
238+ {
239+ if ( ! element . TryGetProperty ( "magicSignatures" , out var signatures ) || signatures . ValueKind != JsonValueKind . Array )
240+ {
241+ return "Array.Empty<MimeMagicSignature>()" ;
242+ }
243+
244+ var items = new List < string > ( ) ;
245+ foreach ( var signature in signatures . EnumerateArray ( ) )
246+ {
247+ if ( signature . ValueKind != JsonValueKind . Object )
248+ {
249+ continue ;
250+ }
251+
252+ var raw = GetString ( signature , "raw" ) ;
253+ if ( string . IsNullOrWhiteSpace ( raw ) )
254+ {
255+ continue ;
256+ }
257+
258+ var bytes = GetByteArray ( signature , "bytes" ) ;
259+ var hex = GetString ( signature , "hex" ) ;
260+ var offset = GetInt ( signature , "offset" ) ;
261+ items . Add ( $@ "new MimeMagicSignature
262+ {{
263+ Raw = { Literal ( raw ) } ,
264+ Bytes = { ByteArray ( bytes ) } ,
265+ Hex = { Literal ( hex ) } ,
266+ Offset = { offset . ToString ( System . Globalization . CultureInfo . InvariantCulture ) }
267+ }}" ) ;
268+ }
269+
270+ return items . Count == 0
271+ ? "Array.Empty<MimeMagicSignature>()"
272+ : "new[]\n {\n " + string . Join ( ",\n " , items ) + "\n }" ;
273+ }
274+
275+ private static string StringArray ( IReadOnlyList < string > values )
276+ {
277+ return values . Count == 0
278+ ? "Array.Empty<string>()"
279+ : "new[] { " + string . Join ( ", " , values . Select ( Literal ) ) + " }" ;
280+ }
281+
282+ private static string ByteArray ( IReadOnlyList < byte > values )
283+ {
284+ return values . Count == 0
285+ ? "ImmutableArray<byte>.Empty"
286+ : "ImmutableArray.Create<byte>(" + string . Join ( ", " , values . Select ( static value => $ "0x{ value : X2} ") ) + ")" ;
287+ }
288+
289+ private static IReadOnlyList < string > GetStringArray ( JsonElement element , string propertyName )
290+ {
291+ if ( ! element . TryGetProperty ( propertyName , out var property ) || property . ValueKind != JsonValueKind . Array )
292+ {
293+ return Array . Empty < string > ( ) ;
294+ }
295+
296+ var values = new List < string > ( ) ;
297+ foreach ( var item in property . EnumerateArray ( ) )
298+ {
299+ if ( item . ValueKind == JsonValueKind . String )
300+ {
301+ var value = item . GetString ( ) ;
302+ if ( ! string . IsNullOrWhiteSpace ( value ) )
303+ {
304+ values . Add ( value ) ;
305+ }
306+ }
307+ }
308+
309+ return values ;
310+ }
311+
312+ private static IReadOnlyList < byte > GetByteArray ( JsonElement element , string propertyName )
313+ {
314+ if ( ! element . TryGetProperty ( propertyName , out var property ) || property . ValueKind != JsonValueKind . Array )
315+ {
316+ return Array . Empty < byte > ( ) ;
317+ }
318+
319+ var values = new List < byte > ( ) ;
320+ foreach ( var item in property . EnumerateArray ( ) )
321+ {
322+ if ( item . ValueKind == JsonValueKind . Number && item . TryGetByte ( out var value ) )
323+ {
324+ values . Add ( value ) ;
325+ }
326+ }
327+
328+ return values ;
329+ }
330+
331+ private static string ? GetString ( JsonElement element , string propertyName )
332+ {
333+ return element . TryGetProperty ( propertyName , out var property ) && property . ValueKind == JsonValueKind . String
334+ ? property . GetString ( )
335+ : null ;
336+ }
337+
338+ private static bool GetBool ( JsonElement element , string propertyName )
339+ {
340+ return element . TryGetProperty ( propertyName , out var property ) &&
341+ property . ValueKind is JsonValueKind . True or JsonValueKind . False &&
342+ property . GetBoolean ( ) ;
343+ }
344+
345+ private static int GetInt ( JsonElement element , string propertyName )
346+ {
347+ return element . TryGetProperty ( propertyName , out var property ) &&
348+ property . ValueKind == JsonValueKind . Number &&
349+ property . TryGetInt32 ( out var value )
350+ ? value
351+ : 0 ;
352+ }
353+
354+ private static string BoolLiteral ( bool value )
355+ {
356+ return value ? "true" : "false" ;
357+ }
358+
139359 private static string ParseKey ( string key )
140360 {
141361 if ( char . IsDigit ( key [ 0 ] ) )
@@ -148,10 +368,17 @@ private static string ParseKey(string key)
148368 return key . ToUpperInvariant ( ) ;
149369 }
150370
371+ private static string Literal ( string ? value )
372+ {
373+ return value == null ? "null" : $ "\" { Escape ( value ) } \" ";
374+ }
375+
151376 private static string Escape ( string value )
152377 {
153378 return value
154379 . Replace ( "\\ " , "\\ \\ " )
155- . Replace ( "\" " , "\\ \" " ) ;
380+ . Replace ( "\" " , "\\ \" " )
381+ . Replace ( "\r " , "\\ r" )
382+ . Replace ( "\n " , "\\ n" ) ;
156383 }
157384}
0 commit comments