Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Automatically recompile the lib if the compile time database is out o…
…f date (#28)
- Loading branch information
Showing
7 changed files
with
227 additions
and
183 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,174 +1,2 @@ | ||
defmodule MIME do | ||
@moduledoc """ | ||
Maps MIME types to its file extensions and vice versa. | ||
MIME types can be extended in your application configuration | ||
as follows: | ||
config :mime, :types, %{ | ||
"application/vnd.api+json" => ["json-api"] | ||
} | ||
After adding the configuration, MIME needs to be recompiled. | ||
If you are using mix, it can be done with: | ||
$ mix deps.clean mime --build | ||
$ mix deps.get | ||
""" | ||
|
||
@compile :no_native | ||
@external_resource "lib/mime.types" | ||
|
||
stream = File.stream!("lib/mime.types") | ||
|
||
# TODO: remove when we depend on Elixir ~> 1.3. | ||
string_trim = | ||
if Code.ensure_loaded?(String) and function_exported?(String, :trim, 1) do | ||
&String.trim/1 | ||
else | ||
&apply(String, :strip, [&1]) | ||
end | ||
|
||
mapping = | ||
Enum.flat_map(stream, fn line -> | ||
if String.starts_with?(line, ["#", "\n"]) do | ||
[] | ||
else | ||
[type | exts] = | ||
line | ||
|> string_trim.() | ||
|> String.split() | ||
|
||
[{type, exts}] | ||
end | ||
end) | ||
|
||
app = Application.get_env(:mime, :types, %{}) | ||
|
||
@doc """ | ||
Returns whether a MIME type is registered. | ||
## Examples | ||
iex> MIME.valid?("text/plain") | ||
true | ||
iex> MIME.valid?("foo/bar") | ||
false | ||
""" | ||
@spec valid?(String.t()) :: boolean | ||
def valid?(type) do | ||
is_list(mime_to_ext(type)) | ||
end | ||
|
||
@doc """ | ||
Returns the extensions associated with a given MIME type. | ||
## Examples | ||
iex> MIME.extensions("text/html") | ||
["html", "htm"] | ||
iex> MIME.extensions("application/json") | ||
["json"] | ||
iex> MIME.extensions("foo/bar") | ||
[] | ||
""" | ||
@spec extensions(String.t()) :: [String.t()] | ||
def extensions(type) do | ||
mime_to_ext(type) || [] | ||
end | ||
|
||
@default_type "application/octet-stream" | ||
|
||
@doc """ | ||
Returns the MIME type associated with a file extension. | ||
If no MIME type is known for `file_extension`, | ||
`#{inspect(@default_type)}` is returned. | ||
## Examples | ||
iex> MIME.type("txt") | ||
"text/plain" | ||
iex> MIME.type("foobarbaz") | ||
#{inspect(@default_type)} | ||
""" | ||
@spec type(String.t()) :: String.t() | ||
def type(file_extension) do | ||
ext_to_mime(file_extension) || @default_type | ||
end | ||
|
||
@doc """ | ||
Returns whether an extension has a MIME type registered. | ||
## Examples | ||
iex> MIME.has_type?("txt") | ||
true | ||
iex> MIME.has_type?("foobarbaz") | ||
false | ||
""" | ||
@spec has_type?(String.t()) :: boolean | ||
def has_type?(file_extension) do | ||
is_binary(ext_to_mime(file_extension)) | ||
end | ||
|
||
@doc """ | ||
Guesses the MIME type based on the path's extension. See `type/1`. | ||
## Examples | ||
iex> MIME.from_path("index.html") | ||
"text/html" | ||
""" | ||
@spec from_path(Path.t()) :: String.t() | ||
def from_path(path) do | ||
case Path.extname(path) do | ||
"." <> ext -> type(downcase(ext, "")) | ||
_ -> @default_type | ||
end | ||
end | ||
|
||
defp downcase(<<h, t::binary>>, acc) when h in ?A..?Z, do: downcase(t, <<acc::binary, h + 32>>) | ||
defp downcase(<<h, t::binary>>, acc), do: downcase(t, <<acc::binary, h>>) | ||
defp downcase(<<>>, acc), do: acc | ||
|
||
@spec ext_to_mime(String.t()) :: String.t() | nil | ||
defp ext_to_mime(type) | ||
|
||
# The ones from the app always come first. | ||
for {type, exts} <- app, | ||
ext <- List.wrap(exts) do | ||
defp ext_to_mime(unquote(ext)), do: unquote(type) | ||
end | ||
|
||
for {type, exts} <- mapping, | ||
ext <- exts do | ||
defp ext_to_mime(unquote(ext)), do: unquote(type) | ||
end | ||
|
||
defp ext_to_mime(_ext), do: nil | ||
|
||
@spec mime_to_ext(String.t()) :: list(String.t()) | nil | ||
defp mime_to_ext(type) | ||
|
||
for {type, exts} <- app do | ||
defp mime_to_ext(unquote(type)), do: unquote(List.wrap(exts)) | ||
end | ||
|
||
for {type, exts} <- mapping do | ||
defp mime_to_ext(unquote(type)), do: unquote(exts) | ||
end | ||
|
||
defp mime_to_ext(_type), do: nil | ||
end | ||
quoted = MIME.Application.quoted(Application.get_env(:mime, :types, %{})) | ||
Module.create(MIME, quoted, __ENV__) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
defmodule MIME.Application do | ||
@moduledoc false | ||
|
||
use Application | ||
require Logger | ||
|
||
def start(_, _) do | ||
app = Application.fetch_env!(:mime, :types) | ||
|
||
if app != MIME.compiled_custom_types() do | ||
Logger.error(""" | ||
The :mime library has been compiled with the following custom types: | ||
#{inspect(MIME.compiled_custom_types())} | ||
But it is being started with the following types: | ||
#{inspect(app)} | ||
We are going to dynamically recompile it during boot, | ||
but please clean the :mime dependency to make sure it is recompiled: | ||
$ mix deps.clean mime --build | ||
""") | ||
|
||
Module.create(MIME, quoted(app), __ENV__) | ||
end | ||
|
||
Supervisor.start_link([], strategy: :one_for_one) | ||
end | ||
|
||
def quoted(custom_types) do | ||
quote bind_quoted: [custom_types: Macro.escape(custom_types)] do | ||
@moduledoc """ | ||
Maps MIME types to its file extensions and vice versa. | ||
MIME types can be extended in your application configuration | ||
as follows: | ||
config :mime, :types, %{ | ||
"application/vnd.api+json" => ["json-api"] | ||
} | ||
After adding the configuration, MIME needs to be recompiled. | ||
If you are using mix, it can be done with: | ||
$ mix deps.clean mime --build | ||
$ mix deps.get | ||
""" | ||
|
||
mime_file = Application.app_dir(:mime, "priv/mime.types") | ||
@compile :no_native | ||
@external_resource mime_file | ||
stream = File.stream!(mime_file) | ||
|
||
mapping = | ||
Enum.flat_map(stream, fn line -> | ||
if String.starts_with?(line, ["#", "\n"]) do | ||
[] | ||
else | ||
[type | exts] = | ||
line | ||
|> String.trim() | ||
|> String.split() | ||
|
||
[{type, exts}] | ||
end | ||
end) | ||
|
||
@doc """ | ||
Returns the custom types compiled into the MIME module. | ||
""" | ||
def compiled_custom_types do | ||
unquote(Macro.escape(custom_types)) | ||
end | ||
|
||
@doc """ | ||
Returns whether a MIME type is registered. | ||
## Examples | ||
iex> MIME.valid?("text/plain") | ||
true | ||
iex> MIME.valid?("foo/bar") | ||
false | ||
""" | ||
@spec valid?(String.t()) :: boolean | ||
def valid?(type) do | ||
is_list(mime_to_ext(type)) | ||
end | ||
|
||
@doc """ | ||
Returns the extensions associated with a given MIME type. | ||
## Examples | ||
iex> MIME.extensions("text/html") | ||
["html", "htm"] | ||
iex> MIME.extensions("application/json") | ||
["json"] | ||
iex> MIME.extensions("foo/bar") | ||
[] | ||
""" | ||
@spec extensions(String.t()) :: [String.t()] | ||
def extensions(type) do | ||
mime_to_ext(type) || [] | ||
end | ||
|
||
@default_type "application/octet-stream" | ||
|
||
@doc """ | ||
Returns the MIME type associated with a file extension. | ||
If no MIME type is known for `file_extension`, | ||
`#{inspect(@default_type)}` is returned. | ||
## Examples | ||
iex> MIME.type("txt") | ||
"text/plain" | ||
iex> MIME.type("foobarbaz") | ||
#{inspect(@default_type)} | ||
""" | ||
@spec type(String.t()) :: String.t() | ||
def type(file_extension) do | ||
ext_to_mime(file_extension) || @default_type | ||
end | ||
|
||
@doc """ | ||
Returns whether an extension has a MIME type registered. | ||
## Examples | ||
iex> MIME.has_type?("txt") | ||
true | ||
iex> MIME.has_type?("foobarbaz") | ||
false | ||
""" | ||
@spec has_type?(String.t()) :: boolean | ||
def has_type?(file_extension) do | ||
is_binary(ext_to_mime(file_extension)) | ||
end | ||
|
||
@doc """ | ||
Guesses the MIME type based on the path's extension. See `type/1`. | ||
## Examples | ||
iex> MIME.from_path("index.html") | ||
"text/html" | ||
""" | ||
@spec from_path(Path.t()) :: String.t() | ||
def from_path(path) do | ||
case Path.extname(path) do | ||
"." <> ext -> type(downcase(ext, "")) | ||
_ -> @default_type | ||
end | ||
end | ||
|
||
defp downcase(<<h, t::binary>>, acc) when h in ?A..?Z, | ||
do: downcase(t, <<acc::binary, h + 32>>) | ||
|
||
defp downcase(<<h, t::binary>>, acc), do: downcase(t, <<acc::binary, h>>) | ||
defp downcase(<<>>, acc), do: acc | ||
|
||
@spec ext_to_mime(String.t()) :: String.t() | nil | ||
defp ext_to_mime(type) | ||
|
||
# The ones from the app always come first. | ||
for {type, exts} <- custom_types, | ||
ext <- List.wrap(exts) do | ||
defp ext_to_mime(unquote(ext)), do: unquote(type) | ||
end | ||
|
||
for {type, exts} <- mapping, | ||
ext <- exts do | ||
defp ext_to_mime(unquote(ext)), do: unquote(type) | ||
end | ||
|
||
defp ext_to_mime(_ext), do: nil | ||
|
||
@spec mime_to_ext(String.t()) :: list(String.t()) | nil | ||
defp mime_to_ext(type) | ||
|
||
for {type, exts} <- custom_types do | ||
defp mime_to_ext(unquote(type)), do: unquote(List.wrap(exts)) | ||
end | ||
|
||
for {type, exts} <- mapping do | ||
defp mime_to_ext(unquote(type)), do: unquote(exts) | ||
end | ||
|
||
defp mime_to_ext(_type), do: nil | ||
end | ||
end | ||
end |
Oops, something went wrong.