136 lines
4.2 KiB
Elixir
136 lines
4.2 KiB
Elixir
|
defmodule Mix.Tasks.Gettext.Extract do
|
||
|
use Mix.Task
|
||
|
@recursive true
|
||
|
|
||
|
@shortdoc "Extracts messages from source code"
|
||
|
|
||
|
@moduledoc """
|
||
|
Extracts messages by recompiling the Elixir source code.
|
||
|
|
||
|
```bash
|
||
|
mix gettext.extract [OPTIONS]
|
||
|
```
|
||
|
|
||
|
messages are extracted into POT (Portable Object Template) files with a
|
||
|
`.pot` extension. The location of these files is determined by the `:otp_app`
|
||
|
and `:priv` options given by Gettext modules when they call `use Gettext`. One
|
||
|
POT file is generated for each message domain.
|
||
|
|
||
|
All automatically-extracted messages are assigned the `elixir-autogen` flag.
|
||
|
If a message from the POT is no longer present and has the `elixir-autogen`
|
||
|
flag, the message is removed.
|
||
|
|
||
|
Before `v0.19.0`, the `elixir-format` flag was used to detect automatically
|
||
|
extracted messages. This has been deprecated in `v0.19.0`. When extracting
|
||
|
with the newest version, the new `elixir-autogen` flag is added to all
|
||
|
automatically extracted messages.
|
||
|
|
||
|
All messages are assigned a format flag. When using the default
|
||
|
interpolation module, that flag is `elixir-format`. With other interpolation
|
||
|
modules, the flag name is defined by that implementation (see
|
||
|
`c:Gettext.Interpolation.message_format/0`).
|
||
|
|
||
|
If you would like to verify that your POT files are up to date with the
|
||
|
current state of the codebase, you can provide the `--check-up-to-date`
|
||
|
flag. This is particularly useful for automated checks and in CI systems.
|
||
|
This validation will fail even when the same calls to Gettext
|
||
|
only change location in the codebase:
|
||
|
|
||
|
```bash
|
||
|
mix gettext.extract --check-up-to-date
|
||
|
```
|
||
|
|
||
|
It is possible to pass the `--merge` option to perform merging
|
||
|
for every Gettext backend updated during merge:
|
||
|
|
||
|
```bash
|
||
|
mix gettext.extract --merge
|
||
|
```
|
||
|
|
||
|
All other options passed to `gettext.extract` are forwarded to the
|
||
|
`gettext.merge` task (`Mix.Tasks.Gettext.Merge`), which is called internally
|
||
|
by this task. For example:
|
||
|
|
||
|
```bash
|
||
|
mix gettext.extract --merge --no-fuzzy
|
||
|
```
|
||
|
|
||
|
"""
|
||
|
|
||
|
@switches [merge: :boolean, check_up_to_date: :boolean]
|
||
|
|
||
|
@impl true
|
||
|
def run(args) do
|
||
|
Application.ensure_all_started(:gettext)
|
||
|
_ = Mix.Project.get!()
|
||
|
mix_config = Mix.Project.config()
|
||
|
{opts, _} = OptionParser.parse!(args, switches: @switches)
|
||
|
pot_files = extract(mix_config[:app], mix_config[:gettext] || [])
|
||
|
|
||
|
if opts[:check_up_to_date] do
|
||
|
run_up_to_date_check(pot_files)
|
||
|
else
|
||
|
run_message_extraction(pot_files, opts, args)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defp run_message_extraction(pot_files, opts, args) do
|
||
|
for {path, contents} <- pot_files do
|
||
|
File.mkdir_p!(Path.dirname(path))
|
||
|
File.write!(path, contents)
|
||
|
Mix.shell().info("Extracted #{Path.relative_to_cwd(path)}")
|
||
|
end
|
||
|
|
||
|
if opts[:merge] do
|
||
|
run_merge(pot_files, args)
|
||
|
end
|
||
|
|
||
|
:ok
|
||
|
end
|
||
|
|
||
|
defp run_up_to_date_check(pot_files) do
|
||
|
not_extracted_paths = for {path, _contents} <- pot_files, do: path
|
||
|
|
||
|
if pot_files == [] do
|
||
|
:ok
|
||
|
else
|
||
|
Mix.raise("""
|
||
|
mix gettext.extract failed due to --check-up-to-date.
|
||
|
The following POT files were not extracted or are out of date:
|
||
|
|
||
|
#{Enum.map_join(not_extracted_paths, "\n", &" * #{&1 |> Path.relative_to_cwd()}")}
|
||
|
""")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defp extract(app, gettext_config) do
|
||
|
Gettext.Extractor.enable()
|
||
|
force_compile()
|
||
|
Gettext.Extractor.pot_files(app, gettext_config)
|
||
|
after
|
||
|
Gettext.Extractor.disable()
|
||
|
end
|
||
|
|
||
|
defp force_compile do
|
||
|
Mix.Tasks.Compile.Elixir.clean()
|
||
|
Enum.each(Mix.Tasks.Compile.Elixir.manifests(), &File.rm/1)
|
||
|
|
||
|
# If "compile" was never called, the reenabling is a no-op and
|
||
|
# "compile.elixir" is a no-op as well (because it wasn't reenabled after
|
||
|
# running "compile"). If "compile" was already called, then running
|
||
|
# "compile" is a no-op and running "compile.elixir" will work because we
|
||
|
# manually reenabled it.
|
||
|
Mix.Task.reenable("compile.elixir")
|
||
|
Mix.Task.run("compile")
|
||
|
Mix.Task.run("compile.elixir", ["--force"])
|
||
|
end
|
||
|
|
||
|
defp run_merge(pot_files, argv) do
|
||
|
pot_files
|
||
|
|> Enum.map(fn {path, _} -> Path.dirname(path) end)
|
||
|
|> Enum.uniq()
|
||
|
|> Task.async_stream(&Mix.Tasks.Gettext.Merge.run([&1 | argv]), ordered: false)
|
||
|
|> Stream.run()
|
||
|
end
|
||
|
end
|