Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use elixir formatter #54

Merged
merged 3 commits into from
Oct 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,21 @@ jobs:
- image: circleci/elixir:1.6
- image: circleci/postgres:9.4
"elixir-1.7":
<<: *shared
# Steps duplicated from the *shared node to check formatting
steps:
- checkout
# Set up the repository and fetch deps
- run: mix local.hex --force
- run: mix local.rebar --force
- run: cp config/test.ci.exs config/test.exs
- run: mix deps.get
# Check source code format
- run: mix format --check-formatted
# Create the test database and run the tests
- run: mix ecto.create
- run: mix test
docker:
- image: circleci/elixir:1.6
- image: circleci/elixir:1.7
- image: circleci/postgres:9.4

workflows:
Expand Down
4 changes: 4 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
import_deps: [:ecto]
]
4 changes: 2 additions & 2 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ use Mix.Config
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
if File.exists?("config/#{Mix.env}.exs") do
import_config("#{Mix.env}.exs")
if File.exists?("config/#{Mix.env()}.exs") do
import_config("#{Mix.env()}.exs")
end
58 changes: 40 additions & 18 deletions lib/trans.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,16 @@ defmodule Trans do

defmacro __using__(opts) do
quote do
Module.put_attribute __MODULE__, :trans_fields, unquote(translatable_fields(opts))
Module.put_attribute __MODULE__, :trans_container, unquote(translation_container(opts))
Module.put_attribute(__MODULE__, :trans_fields, unquote(translatable_fields(opts)))
Module.put_attribute(__MODULE__, :trans_container, unquote(translation_container(opts)))

@after_compile {Trans, :__validate_translatable_fields__}
@after_compile {Trans, :__validate_translation_container__}

Module.eval_quoted __ENV__, [
Module.eval_quoted(__ENV__, [
Trans.__fields__(@trans_fields),
Trans.__container__(@trans_container)
]
])
end
end

Expand Down Expand Up @@ -113,13 +113,16 @@ defmodule Trans do
iex> Trans.translatable?(%Article{}, :not_existing)
false
"""
@spec translatable?(module | struct, String.t | atom) :: boolean
@spec translatable?(module | struct, String.t() | atom) :: boolean
def translatable?(%{__struct__: module}, field), do: translatable?(module, field)
def translatable?(module_or_struct, field) when is_atom(module_or_struct) and is_binary(field) do

def translatable?(module_or_struct, field)
when is_atom(module_or_struct) and is_binary(field) do
translatable?(module_or_struct, String.to_atom(field))
end

def translatable?(module_or_struct, field) when is_atom(module_or_struct) and is_atom(field) do
if Keyword.has_key?(module_or_struct.__info__(:functions), :__trans__) do
if Keyword.has_key?(module_or_struct.__info__(:functions), :__trans__) do
Enum.member?(module_or_struct.__trans__(:fields), field)
else
raise "#{module_or_struct} must use `Trans` in order to be translated"
Expand All @@ -146,40 +149,59 @@ defmodule Trans do
def __validate_translatable_fields__(%{module: module}, _bytecode) do
struct_fields =
module.__struct__()
|> Map.keys
|> MapSet.new
|> Map.keys()
|> MapSet.new()

translatable_fields =
:fields
|> module.__trans__
|> MapSet.new
|> MapSet.new()

invalid_fields = MapSet.difference(translatable_fields, struct_fields)

case MapSet.size(invalid_fields) do
0 -> nil
1 -> raise ArgumentError, message: "#{module} declares '#{MapSet.to_list(invalid_fields)}' as translatable but it is not defined in the module's struct"
_ -> raise ArgumentError, message: "#{module} declares '#{MapSet.to_list(invalid_fields)}' as translatable but it they not defined in the module's struct"
0 ->
nil

1 ->
raise ArgumentError,
message:
"#{module} declares '#{MapSet.to_list(invalid_fields)}' as translatable but it is not defined in the module's struct"

_ ->
raise ArgumentError,
message:
"#{module} declares '#{MapSet.to_list(invalid_fields)}' as translatable but it they not defined in the module's struct"
end
end

@doc false
def __validate_translation_container__(%{module: module}, _bytecode) do
container = module.__trans__(:container)

unless Enum.member?(Map.keys(module.__struct__()), container) do
raise ArgumentError, message: "The field #{container} used as the translation container is not defined in #{module} struct"
raise ArgumentError,
message:
"The field #{container} used as the translation container is not defined in #{module} struct"
end
end

defp translatable_fields(opts) do
case Keyword.fetch(opts, :translates) do
{:ok, fields} when is_list(fields) -> fields
_ -> raise ArgumentError, message: "Trans requires a 'translates' option that contains the list of translatable fields names"
{:ok, fields} when is_list(fields) ->
fields

_ ->
raise ArgumentError,
message:
"Trans requires a 'translates' option that contains the list of translatable fields names"
end
end

defp translation_container(opts) do
case Keyword.fetch(opts, :container) do
:error -> :translations
:error -> :translations
{:ok, container} -> container
end
end

end
25 changes: 19 additions & 6 deletions lib/trans/query_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,22 @@ if Code.ensure_loaded?(Ecto.Query) do

defp generate_query(schema, module, nil, locale) do
quote do
fragment("(?->?)", field(unquote(schema), unquote(module.__trans__(:container))), ^to_string(unquote(locale)))
fragment(
"(?->?)",
field(unquote(schema), unquote(module.__trans__(:container))),
^to_string(unquote(locale))
)
end
end

defp generate_query(schema, module, field, locale) do
quote do
fragment("(?->?->>?)", field(unquote(schema), unquote(module.__trans__(:container))), ^to_string(unquote(locale)), ^unquote(field))
fragment(
"(?->?->>?)",
field(unquote(schema), unquote(module.__trans__(:container))),
^to_string(unquote(locale)),
^unquote(field)
)
end
end

Expand All @@ -106,12 +115,16 @@ if Code.ensure_loaded?(Ecto.Query) do

defp validate_field(module, field) do
cond do
is_nil(field) -> nil
is_nil(field) ->
nil

not Trans.translatable?(module, field) ->
raise ArgumentError, message: "'#{inspect(module)}' module must declare '#{field}' as translatable"
true -> nil
raise ArgumentError,
message: "'#{inspect(module)}' module must declare '#{field}' as translatable"

true ->
nil
end
end

end
end
11 changes: 6 additions & 5 deletions lib/trans/translator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ defmodule Trans.Translator do
"""
@spec translate(struct, atom, atom) :: any
def translate(%{__struct__: module} = struct, field, locale)
when is_atom(locale) and is_atom(field) do
when is_atom(locale) and is_atom(field) do
unless Trans.translatable?(struct, field) do
raise "'#{inspect(module)}' module must declare '#{inspect(field)}' as translatable"
end

# Return the translation or fall back to the default value
case translated_field(struct, locale, field) do
:error -> Map.fetch!(struct, field)
Expand All @@ -74,10 +75,10 @@ defmodule Trans.Translator do
end

defp translated_field(%{__struct__: module} = struct, locale, field) do
with {:ok, all_translations} <- Map.fetch(struct, module.__trans__(:container)),
with {:ok, all_translations} <- Map.fetch(struct, module.__trans__(:container)),
{:ok, translations_for_locale} <- Map.fetch(all_translations, to_string(locale)),
{:ok, translated_field} <- Map.fetch(translations_for_locale, to_string(field)),
do: translated_field
{:ok, translated_field} <- Map.fetch(translations_for_locale, to_string(field)) do
translated_field
end
end

end
37 changes: 21 additions & 16 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@ defmodule Trans.Mixfile do
@version "2.0.3"

def project do
[app: :trans,
version: @version,
elixir: "~> 1.4",
description: "Embedded translations for Elixir schemas",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
elixirc_paths: elixirc_paths(Mix.env),
app_list: app_list(Mix.env),
package: package(),
deps: deps(),
# Docs
name: "Trans",
docs: [source_ref: "v#{@version}", main: "Trans",
canonical: "https://hexdocs.pm/trans",
source_url: "https://github.com/crbelaus/trans"]]
[
app: :trans,
version: @version,
elixir: "~> 1.4",
description: "Embedded translations for Elixir schemas",
build_embedded: Mix.env() == :prod,
start_permanent: Mix.env() == :prod,
elixirc_paths: elixirc_paths(Mix.env()),
app_list: app_list(Mix.env()),
package: package(),
deps: deps(),
# Docs
name: "Trans",
docs: [
source_ref: "v#{@version}",
main: "Trans",
canonical: "https://hexdocs.pm/trans",
source_url: "https://github.com/crbelaus/trans"
]
]
end

# Configuration for the OTP application
Expand All @@ -43,7 +48,7 @@ defmodule Trans.Mixfile do
{:postgrex, "~> 0.11", optional: true},
{:ecto, "~> 2.1", optional: true},
{:ex_doc, ">= 0.0.0", only: :dev},
{:faker, "~> 0.7.0", only: :test},
{:faker, "~> 0.7.0", only: :test}
]
end

Expand Down
Loading