Skip to content

Commit

Permalink
Automatically recompile the lib if the compile time database is out o…
Browse files Browse the repository at this point in the history
…f date (#28)
  • Loading branch information
josevalim committed May 28, 2018
1 parent c627996 commit a80138e
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 183 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Expand Up @@ -4,8 +4,8 @@ elixir: 1.6.1
otp_release: 20.1
matrix:
include:
- otp_release: 17.5
elixir: 1.0.5
- otp_release: 18.0
elixir: 1.3.4
script:
- if [[ `elixir -v` = *"1.6"* ]]; then mix format --check-formatted; fi
- mix test
Expand Down
3 changes: 0 additions & 3 deletions config/config.exs

This file was deleted.

176 changes: 2 additions & 174 deletions lib/mime.ex
@@ -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__)
207 changes: 207 additions & 0 deletions lib/mime/application.ex
@@ -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

0 comments on commit a80138e

Please sign in to comment.