Skip to content

Commit

Permalink
🌼
Browse files Browse the repository at this point in the history
  • Loading branch information
fonsp committed Mar 21, 2022
1 parent 2bd3176 commit bcd32fd
Show file tree
Hide file tree
Showing 7 changed files with 645 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Manifest.toml
.DS_Store
6 changes: 6 additions & 0 deletions Artifacts.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[mimedb]
git-tree-sha1 = "6ac9178816f707e7ed89993630f34ab12ac0176e"

[[mimedb.download]]
sha256 = "b8e70bb4d52acd5d0d1ed848c0e6e3c903a533aa500acffbe003f011b18f9e3b"
url = "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
20 changes: 20 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name = "MIMEs"
uuid = "6c6e2e6c-3030-632d-7369-2d6c69616d65"
license = "MIT"
authors = ["Fons van der Plas <[email protected]>"]
version = "0.1.0"

[deps]
Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"

[compat]
JSON = "0.21"
julia = "1.3"

[extras]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Random"]
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,22 @@
# MIMEs
Get a MIME type from a filename!
# MIMEs.jl
A small package to transform between file extensions and MIME types.

# Examples
```julia
julia> using MIMEs

julia> m = mime_from_extension(".json")
MIME type application/json

julia> extension_from_mime(m)
".json"

julia> contenttype_from_mime(m)
"application/json; charset=utf-8"
```

# Implementation

This package uses the [jshttp/mime-db](https://github.com/jshttp/mime-db) database, made available using Artifacts. The function implementations, including resolution for conflicting extensions, is based on [jshttp/mime-types](https://github.com/jshttp/mime-types).

The database parsing and processing happens during precompilation, lookups are very fast.
207 changes: 207 additions & 0 deletions src/MIMEs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
module MIMEs

export mime_from_extension, mime_from_path, extension_from_mime, charset_from_mime, compressible_from_mime, contenttype_from_mime

import JSON
using Artifacts


const _mimedb_root = artifact"mimedb"

const _mimedb_json = joinpath(_mimedb_root, "package", "db.json")
const _mimedb = JSON.parse(read(_mimedb_json, String))

# https://github.com/jshttp/mime-db/issues/194
_mimedb["text/javascript"], _mimedb["application/javascript"] = _mimedb["application/javascript"], _mimedb["text/javascript"]


const _source_preference = ("nginx", "apache", nothing, "iana")

const _ext2mime = Dict{String,String}()
const _mime2ext = Dict{String,Vector}()

for (mime_str, val) in _mimedb
mime = mime_str

exts = get(val, "extensions", nothing)
if exts === nothing
continue
end
_mime2ext[mime] = exts

src = get(val, "source", nothing)
for ex in exts
if haskey(_ext2mime, ex)
other_src = get(_mimedb[identity(_ext2mime[ex])], "source", nothing)

from = findfirst(isequal(other_src), _source_preference)
to = findfirst(isequal(src), _source_preference)

if (
!(_ext2mime[ex] isa MIME"application/octet-stream") &&
(
from > to ||
(from == to && startswith(identity(_ext2mime[ex]), "application/")
)
)
)
# skip the remapping
continue
end
end

_ext2mime[ex] = mime
end

end

"""
```julia
mime_from_extension(query::String[, default=nothing])::MIME
```
# Examples:
```julia
mime_from_extension(".json") == MIME"application/json"()
mime_from_extension("html") == MIME"text/html"()
mime_from_extension("asdfff") == nothing
mime_from_extension("asdfff", MIME"text/plain"()) == MIME"text/plain"()
```
"""
function mime_from_extension(query::String, default=nothing)
m = get(_ext2mime, lowercase(lstrip(query, '.')), nothing)
m === nothing ? default : MIME(m)
end


"""
```julia
mime_from_path(path::String[, default::T=nothing])::Union{MIME,T}
```
Return the MIME type of the file at `path`, based on the file extension.
# Examples:
```julia
mime_from_path("hello.json") == MIME"application/json"()
mime_from_path("/home/fons/wow.html") == MIME"text/html"()
mime_from_path("/home/fons/wow") == nothing
mime_from_path("/home/fons/wow", MIME"text/plain"()) == MIME"text/plain"()
```
"""
function mime_from_path(path::String, default=nothing)
mime_from_extension(splitext(path)[2], default)
end


"""
```julia
extension_from_mime(mime::MIME[, default::T=""])::Union{String,T}
```
Return the most common file extension used for files of the given MIME type.
# Examples:
```julia
extension_from_mime(MIME"application/json"()) == ".json"
extension_from_mime(MIME"text/html"()) == ".html"
extension_from_mime(MIME"text/blablablaa"()) == ""
extension_from_mime(MIME"text/blablablaa"(), ".bin") == ".bin"
```
"""
function extension_from_mime(mime::MIME, default="")
exs = get(_mime2ext, string(mime), nothing)
if exs === nothing || isempty(exs)
default
else
"." * first(exs)
end
end




"""
```julia
compressible_from_mime(mime::MIME)::Bool
```
Whether a file of the given MIME type can/should be gzipped.
# Examples:
```julia
compressible_from_mime(MIME"text/html"()) == true
compressible_from_mime(MIME"image/png"()) == false
compressible_from_mime(MIME"text/blablablaa"()) == false
```
"""
function compressible_from_mime(mime::MIME)
c = get(_mimedb, string(mime), nothing)
if c === nothing
false
else
get(c, "compressible", false)
end
end



"""
```julia
charset_from_mime(mime::MIME)::String
```
The default charset associated with this type, if any. If not known, text MIMEs default to "UTF-8". Possible values are: $(Set([get(x, "charset", nothing) for x in (_mimedb |> values)]) |> collect |> string).
# Examples:
```julia
charset_from_mime(MIME"application/json"()) == "UTF-8"
charset_from_mime(MIME"application/x-bogus"()) == nothing
charset_from_mime(MIME"text/blablablaa"()) == "UTF-8" # because it is a `text/` mime
charset_from_mime(MIME"text/blablablaa"(), "LATIN-1") == "LATIN-1"
```
"""
function charset_from_mime(mime::MIME)
fallback() = istextmime(mime) ? "UTF-8" : nothing
c = get(_mimedb, string(mime), nothing)
if c === nothing
fallback()
else
get(fallback, c, "charset")::Union{String,Nothing}
end
end


function charset_from_mime(mime::MIME, default)
c = get(_mimedb, string(mime), nothing)
if c === nothing
default
else
get(c, "charset", default)
end
end



"""
```julia
contenttype_from_mime(mime::MIME)::String
```
Turn a MIME into a Content-Type header value, which might include the `charset` parameter.
# Examples:
```julia
contenttype_from_mime(MIME"application/json"()) == "application/json; charset=utf-8"
contenttype_from_mime(MIME"application/x-bogus"()) == "application/x-bogus"
```
# See also:
[`charset_from_mime`](@ref)
"""
contenttype_from_mime(mime::MIME) = let c = charset_from_mime(mime)
c === nothing ? string(mime) : "$(string(mime)); charset=$(lowercase(c))"
end


end
84 changes: 84 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using Test

using MIMEs

@test mime_from_path("a/foo.txt") === MIME"text/plain"()
@test mime_from_path("a/foo.json") === MIME"application/json"()
@test mime_from_extension(".json") === MIME"application/json"()
@test mime_from_extension("json") === MIME"application/json"()
@test mime_from_extension("JSON") === MIME"application/json"()
@test extension_from_mime(MIME"application/json"()) == ".json"
@test extension_from_mime(MIME"application/x-asfdafd"()) == ""
@test extension_from_mime(MIME"application/x-asfdafd"(), ".a") == ".a"


@test charset_from_mime(MIME"application/x-asfdafd"()) == nothing
@test charset_from_mime(MIME"text/x-asfdafd"()) == "UTF-8"
@test charset_from_mime(MIME"text/plain"()) == "UTF-8"
@test charset_from_mime(MIME"text/html"()) == "UTF-8"
@test contenttype_from_mime(MIME"application/x-asfdafd"()) == "application/x-asfdafd"
@test contenttype_from_mime(MIME"text/html"()) == "text/html; charset=utf-8"

@test mime_from_extension("js") == MIME"text/javascript"()

@test compressible_from_mime(MIME"text/html"())
@test compressible_from_mime(MIME"text/css"())
@test !compressible_from_mime(MIME"text/x-sadfa"())

@test contenttype_from_mime(MIME"application/json"()) == "application/json; charset=utf-8"
@test contenttype_from_mime(MIME"application/x-bogus"()) == "application/x-bogus"

# from https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
const mdn = Dict(
".bin" => "application/octet-stream",
".bmp" => "image/bmp",
".css" => "text/css",
".csv" => "text/csv",
".eot" => "application/vnd.ms-fontobject",
".gz" => "application/gzip",
".gif" => "image/gif",
".htm" => "text/html",
".html" => "text/html",
".ico" => "image/vnd.microsoft.icon",
".jpeg" => "image/jpeg",
".jpg" => "image/jpeg",
".js" => "text/javascript",
".json" => "application/json",
".jsonld" => "application/ld+json",
".mjs" => "text/javascript",
".mp3" => "audio/mpeg",
".mp4" => "video/mp4",
".mpeg" => "video/mpeg",
".ogx" => "application/ogg", ".otf" => "font/otf",
".png" => "image/png",
".pdf" => "application/pdf",
".rtf" => "application/rtf",
".sh" => "application/x-sh",
".svg" => "image/svg+xml",
".tar" => "application/x-tar",
".tif" => "image/tiff",
".tiff" => "image/tiff",
".ttf" => "font/ttf",
".txt" => "text/plain",
".weba" => "audio/webm",
".webm" => "video/webm",
".webp" => "image/webp",
".woff" => "font/woff",
".woff2" => "font/woff2",
".xhtml" => "application/xhtml+xml",
".xml" => "application/xml",
".xul" => "application/vnd.mozilla.xul+xml",
".zip" => "application/zip",
".wasm" => "application/wasm",
)

for (ex, ms) in mdn
@test mime_from_extension(ex) === MIME(ms)
end


# Mismatched with MDN, but that's fine:
# ".aac" => "audio/aac",
# ".wav" => "audio/wav",
# ".oga" => "audio/ogg", ".ogv" => "video/ogg",
# ".opus" => "audio/opus",
Loading

0 comments on commit bcd32fd

Please sign in to comment.