...
 
Commits (8)
......@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- All mime types can be used as constants (e.g. `MimeTypeMap.Image.Png`).
- All extensions can be used as constants (e.g. `MimeTypeMap.Extensions.Png`).
### Changed
......
......@@ -12,13 +12,76 @@ generated from [Apache's mime.types](https://svn.apache.org/repos/asf/httpd/http
## Introduction
### Usage
After installation [PommaLabs.MimeTypes](https://www.nuget.org/packages/PommaLabs.MimeTypes/) package,
include the following using statement in your class:
```cs
using PommaLabs.MimeTypes;
```
#### Getting the mime type of a file path
```cs
Console.WriteLine("txt -> " + MimeTypeMap.GetMimeType("a.txt")); // "txt -> text/plain"
```
Pass in a file path and get a mime type back.
If no mime type is not found then an exception is thrown, unless `throwIfMissing` is set to false.
In that case, the generic `application/octet-stream` mime type is returned.
#### Getting the extension of a mime type
```cs
Console.WriteLine("audio/wav -> " + MimeTypeMap.GetExtension("audio/wav")); // "audio/wav -> .wav"
```
Pass in a mime type and get an extension back. If the mime type is not registered, an error is thrown.
If the mime type is not found then an exception is thrown, unless `throwIfMissing` is set to false.
In that case, the generic `.bin` extension is returned.
#### Mime type constants
```cs
Console.WriteLine(MimeTypeMap.Application.Onenote); // "applcation/onenote"
Console.WriteLine(MimeTypeMap.Image.Png); // "image/png"
Console.WriteLine(MimeTypeMap.Video._3gpp); // "video/3gpp"
Console.WriteLine(MimeTypeMap.GetExtension(MimeTypeMap.Application.Onenote)); // ".one"
Console.WriteLine(MimeTypeMap.GetExtension(MimeTypeMap.Image.Png)); // ".png"
Console.WriteLine(MimeTypeMap.GetExtension(MimeTypeMap.Video._3gpp)); // ".3gpp"
```
Subtypes starting with a digit are prefixed with a `_` character,
since C# field names cannot start with a digit.
#### Extension constants
```cs
Console.WriteLine(MimeTypeMap.Extensions.One); // ".one"
Console.WriteLine(MimeTypeMap.Extensions.Png); // ".png"
Console.WriteLine(MimeTypeMap.Extensions._3gpp); // ".3gpp"
Console.WriteLine(MimeTypeMap.GetMimeType(MimeTypeMap.Extensions.One)); // "application/onenote"
Console.WriteLine(MimeTypeMap.GetMimeType(MimeTypeMap.Extensions.Png)); // "image/png"
Console.WriteLine(MimeTypeMap.GetMimeType(MimeTypeMap.Extensions._3gpp)); // "video/3gpp"
```
Extensions starting with a digit are prefixed with a `_` character,
since C# field names cannot start with a digit.
### Compatibility
This library tries to be as much compatible as possible with [MediaTypeMap.Core](https://www.nuget.org/packages/MediaTypeMap.Core/) package,
with following caveats:
- Namespace is different, in order to avoid name clashes.
- An empty string will not be returned to unknown inputs when `throwIfMissing` is false.
- When `throwIfMissing` is not specified or is true, an exception will be thrown for unknown inputs.
+ This applies to both mime type lookup and extension lookup.
- When `throwIfMissing` is false, an empty string will not be returned to unknown inputs:
+ Missing mime types will receive `.bin` as default extension.
+ Missing extensions will receive `application/octet-stream` as default mime type.
- For valid mime types, the same extension will be returned, with following exceptions:
......@@ -33,6 +96,13 @@ with following caveats:
+ `audio/x-pn-realaudio-plugin` now returns `rmp` instead of `rpm`.
That seemed a typo, since `rpm` stands for Red Hat Package Manager.
This library might look similar to [MimeTypeList](https://www.nuget.org/packages/MimeTypeList/) package,
but there are a few core differences:
- This library exposes mime types and extensions as constants which serve the same purpose of an enum.
On the other hand, [MimeTypeList](https://www.nuget.org/packages/MimeTypeList/) package uses them as a static map.
- Mime types and extensions are exposed with PascalCase names.
## About this repository and its maintainer
Everything done on this repository is freely offered on the terms of the project license. You are free to do everything you want with the code and its related files, as long as you respect the license and use common sense while doing it :-)
......@@ -47,8 +117,9 @@ However, if this project helps you, then you might offer me an hot cup of coffee
This project was inspired by the following open source projects:
- [https://github.com/hey-red/MimeTypesMap](https://github.com/hey-red/MimeTypesMap)
- [https://github.com/samuelneff/MimeTypes](https://github.com/samuelneff/MimeTypes)
- [https://github.com/hey-red/MimeTypesMap](https://github.com/hey-red/MimeTypesMap)
- [https://github.com/wagesj45/MimeTypeList](https://github.com/wagesj45/MimeTypeList)
Moreover, this project heavily relies on the following open source projects:
......
// File name: CustomFilters.cs
//
// Author(s): Alessio Parma <[email protected]>
//
// The MIT License (MIT)
//
// Copyright (c) 2020-2021 Alessio Parma <[email protected]>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
namespace PommaLabs.MimeTypes.Generator
{
/// <summary>
/// Custom filters for DotLiquid.
/// </summary>
internal static class CustomFilters
{
private static readonly char[] s_splitChars = new[] { '-', '+', '.', '_' };
public static string Sharpify(string w)
{
w = CapitalizeParts(w);
return char.IsDigit(w[0]) ? $"_{w}" : w;
}
private static string Capitalize(string w)
{
return $"{char.ToUpperInvariant(w[0])}{w[1..]}";
}
private static string CapitalizeParts(string w)
{
return string.Join(string.Empty, w
.Split(s_splitChars, StringSplitOptions.RemoveEmptyEntries)
.Select(x => Capitalize(x)));
}
}
}
......@@ -48,8 +48,10 @@ namespace PommaLabs.MimeTypes
// Retrieved from: {{ source_url }}
private static readonly StringMap s_mimeTypeMap = new StringMap(StringComparer.OrdinalIgnoreCase)
{
{% for kv in mime_types -%}
["{{ kv.Key }}"] = ".{{ kv.Value }}",
{% for gkv in mime_types -%}
{% for kv in gkv.Value -%}
["{{ gkv.Key }}/{{ kv.Key }}"] = ".{{ kv.Value }}",
{% endfor -%}
{% endfor -%}
};
......@@ -163,22 +165,54 @@ namespace PommaLabs.MimeTypes
: DefaultMimeType;
}
{% for gkv in grouped_mime_types -%}
{% for gkv in mime_types -%}
/// <summary>
/// Mime type constants.
/// </summary>
{% capture class_name -%}{{ gkv.Key | sharpify }}{% endcapture -%}
{% if class_name == "Text" -%}
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Auto generated class name from text/* mime type")]
{% endif -%}
[SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "This is a class with constant fields only")]
public static class {{ gkv.Key }}
public static class {{ class_name }}
{
{% for kv in gkv.Value -%}
{% capture mime_type -%}{{ gkv.Key }}/{{ kv.Key }}{% endcapture -%}
/// <summary>
/// "{{ kv.Value }}" mime type constant.
/// "{{ mime_type }}" mime type constant.
/// </summary>
public const string {{ kv.Key }} = "{{ kv.Value }}";
{% capture field_name -%}{{ kv.Key | sharpify }}{% endcapture -%}
{% if field_name contains "_" -%}
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Field names cannot start with a digit")]
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Field names cannot start with a digit")]
{% endif -%}
public const string {{ field_name }} = "{{ mime_type }}";
{% endfor -%}
}
{% endfor -%}
/// <summary>
/// Extension constants.
/// </summary>
[SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "This is a class with constant fields only")]
public static class Extensions
{
{% for kv in extensions -%}
/// <summary>
/// ".{{ kv.Key }}" extension constant.
/// </summary>
{% capture field_name -%}{{ kv.Key | sharpify }}{% endcapture -%}
{% if field_name contains "_" -%}
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Field names cannot start with a digit")]
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Field names cannot start with a digit")]
{% endif -%}
{% if field_name == "Obj" -%}
[SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = "Auto generated field name from .obj extension")]
{% endif -%}
public const string {{ field_name }} = ".{{ kv.Key }}";
{% endfor -%}
}
}
}
\ No newline at end of file
......@@ -32,8 +32,10 @@ namespace PommaLabs.MimeTypes.UnitTests
private const string DefaultExtension = ".bin";
private const string DefaultMimeType = "application/octet-stream";
{% for kv in mime_types -%}
[TestCase("{{ kv.Key }}", ".{{ kv.Value }}")]
{% for gkv in mime_types -%}
{% for kv in gkv.Value -%}
[TestCase(MimeTypeMap.{{ gkv.Key | sharpify }}.{{ kv.Key | sharpify }}, ".{{ kv.Value }}")]
{% endfor -%}
{% endfor -%}
public void GetExtension_ExistingMimeTypeShouldBeMatchedToCorrectExtension(string mimeType, string extension)
{
......@@ -52,8 +54,10 @@ namespace PommaLabs.MimeTypes.UnitTests
Assert.Throws<ArgumentException>(() => MimeTypeMap.GetExtension("image/bread"));
}
{% for kv in mime_types -%}
[TestCase("{{ kv.Key }}")]
{% for gkv in mime_types -%}
{% for kv in gkv.Value -%}
[TestCase(MimeTypeMap.{{ gkv.Key | sharpify }}.{{ kv.Key | sharpify }})]
{% endfor -%}
{% endfor -%}
public void GetExtension_ResultShouldBeConsistentWithMediaTypeMapCore(string mimeType)
{
......@@ -85,8 +89,10 @@ namespace PommaLabs.MimeTypes.UnitTests
Assert.That(mimeTypesResult, Is.EqualTo(mediaTypeMapCoreResult));
}
{% for kv in mime_types -%}
[TestCase("{{ kv.Key }}", ".{{ kv.Value }}")]
{% for gkv in mime_types -%}
{% for kv in gkv.Value -%}
[TestCase(MimeTypeMap.{{ gkv.Key | sharpify }}.{{ kv.Key | sharpify }}, "{{ kv.Value }}")]
{% endfor -%}
{% endfor -%}
public void GetExtensionWithoutDot_ExistingMimeTypeShouldBeMatchedToCorrectExtension(string mimeType, string extension)
{
......@@ -106,7 +112,7 @@ namespace PommaLabs.MimeTypes.UnitTests
}
{% for kv in extensions -%}
[TestCase(".{{ kv.Key }}", "{{ kv.Value }}")]
[TestCase(MimeTypeMap.Extensions.{{ kv.Key | sharpify }}, "{{ kv.Value }}")]
{% endfor -%}
public void GetMimeType_ExistingExtensionShouldBeMatchedToCorrectMimeType(string extension, string mimeType)
{
......
......@@ -30,7 +30,7 @@ using DotLiquid;
using StringMap = System.Collections.Generic.SortedList<string, string>;
using StringMultiMap = System.Collections.Generic.SortedList<string, System.Collections.Generic.SortedList<string, string>>;
namespace PommaLabs.MimeTypeMap.Generator
namespace PommaLabs.MimeTypes.Generator
{
/// <summary>
/// Source code generator.
......@@ -53,12 +53,14 @@ namespace PommaLabs.MimeTypeMap.Generator
return 1;
}
var (extensions, mimeTypes, groupedMimeTypes) = GetExtensionsAndMimeTypes();
Template.RegisterFilter(typeof(CustomFilters));
var (extensions, mimeTypes) = GetExtensionsAndMimeTypes();
switch (args[0])
{
case "src":
Console.WriteLine(GenerateMimeTypeMap(extensions, mimeTypes, groupedMimeTypes));
Console.WriteLine(GenerateMimeTypeMap(extensions, mimeTypes));
return 0;
case "test":
......@@ -70,7 +72,7 @@ namespace PommaLabs.MimeTypeMap.Generator
}
}
private static string GenerateMimeTypeMap(StringMap extensions, StringMap mimeTypes, StringMultiMap groupedMimeTypes)
private static string GenerateMimeTypeMap(StringMap extensions, StringMultiMap mimeTypes)
{
var templateContents = File.ReadAllText("MimeTypeMap.liquid");
var template = Template.Parse(templateContents);
......@@ -80,11 +82,10 @@ namespace PommaLabs.MimeTypeMap.Generator
source_url = ApacheMimeTypesUrl,
extensions,
mime_types = mimeTypes,
grouped_mime_types = groupedMimeTypes,
}));
}
private static string GenerateMimeTypeMapTests(StringMap extensions, StringMap mimeTypes)
private static string GenerateMimeTypeMapTests(StringMap extensions, StringMultiMap mimeTypes)
{
var templateContents = File.ReadAllText("MimeTypeMapTests.liquid");
var template = Template.Parse(templateContents);
......@@ -96,14 +97,13 @@ namespace PommaLabs.MimeTypeMap.Generator
}));
}
private static (StringMap extensions, StringMap mimeTypes, StringMultiMap groupedMimeTypes) GetExtensionsAndMimeTypes()
private static (StringMap extensions, StringMultiMap mimeTypes) GetExtensionsAndMimeTypes()
{
using var webClient = new WebClient();
var response = webClient.DownloadString(new Uri(ApacheMimeTypesUrl));
var extensions = new StringMap(StringComparer.OrdinalIgnoreCase);
var mimeTypes = new StringMap(StringComparer.OrdinalIgnoreCase);
var groupedMimeTypes = new StringMultiMap(StringComparer.OrdinalIgnoreCase);
var mimeTypes = new StringMultiMap(StringComparer.OrdinalIgnoreCase);
using var reader = new StringReader(response);
......@@ -133,24 +133,19 @@ namespace PommaLabs.MimeTypeMap.Generator
}
if (first)
{
mimeTypes.Add(mimeType, extension);
first = false;
}
}
static string Capitalize(string w) => char.IsDigit(w[0]) ? $"D{w}" : $"{char.ToUpperInvariant(w[0])}{w[1..]}";
static string ToCSharpName(string w) => string.Join("", w.Split('-', '+', '.').Select(x => Capitalize(x)));
var splitMimeType = mimeType.Split('/');
var type = splitMimeType[0];
var subtype = splitMimeType[1];
var splitMimeType = mimeType.Split('/');
var type = ToCSharpName(splitMimeType[0]);
var subtype = ToCSharpName(splitMimeType[1]);
if (!mimeTypes.TryGetValue(type, out var subtypes))
{
mimeTypes[type] = subtypes = new StringMap(StringComparer.OrdinalIgnoreCase);
}
if (!groupedMimeTypes.TryGetValue(type, out var subtypes))
{
groupedMimeTypes[type] = subtypes = new StringMap(StringComparer.OrdinalIgnoreCase);
subtypes.Add(subtype, extension);
first = false;
}
}
subtypes.Add(subtype, mimeType);
}
// Following overrides ensures best compatibility with MediaTypeMap.Core results.
......@@ -158,19 +153,19 @@ namespace PommaLabs.MimeTypeMap.Generator
// simply wrong and they are documented in this project README.
extensions["3gp2"] = "video/3gpp2";
extensions["one"] = "application/onenote";
mimeTypes["application/onenote"] = "one";
mimeTypes["application/postscript"] = "eps";
mimeTypes["application/x-msdownload"] = "dll";
mimeTypes["application/x-texinfo"] = "texi";
mimeTypes["audio/basic"] = "snd";
mimeTypes["audio/mpeg"] = "mp3";
mimeTypes["audio/x-pn-realaudio"] = "ra";
mimeTypes["image/jpeg"] = "jpg";
mimeTypes["video/3gpp2"] = "3gp2";
mimeTypes["video/mpeg"] = "mpg";
mimeTypes["video/quicktime"] = "mov";
return (extensions, mimeTypes, groupedMimeTypes);
mimeTypes["application"]["onenote"] = "one";
mimeTypes["application"]["postscript"] = "eps";
mimeTypes["application"]["x-msdownload"] = "dll";
mimeTypes["application"]["x-texinfo"] = "texi";
mimeTypes["audio"]["basic"] = "snd";
mimeTypes["audio"]["mpeg"] = "mp3";
mimeTypes["audio"]["x-pn-realaudio"] = "ra";
mimeTypes["image"]["jpeg"] = "jpg";
mimeTypes["video"]["3gpp2"] = "3gp2";
mimeTypes["video"]["mpeg"] = "mpg";
mimeTypes["video"]["quicktime"] = "mov";
return (extensions, mimeTypes);
}
}
}
This diff is collapsed.