api-v2/deps/nimble_parsec/lib/mix/tasks/nimble_parsec.compile.ex
2025-04-16 10:03:13 -03:00

119 lines
2.6 KiB
Elixir

defmodule Mix.Tasks.NimbleParsec.Compile do
@shortdoc "Compiles a parser and injects its content into the parser file"
@moduledoc ~S"""
Compiles a parser from a template.
$ mix nimble_parsec.compile template.ex.exs
This task is useful to generate parsers that have no runtime dependency
on NimbleParsec.
## Examples
Let's define a template file:
# lib/my_parser.ex.exs
defmodule MyParser do
@moduledoc false
# parsec:MyParser
import NimbleParsec
date =
integer(4)
|> ignore(string("-"))
|> integer(2)
|> ignore(string("-"))
|> integer(2)
time =
integer(2)
|> ignore(string(":"))
|> integer(2)
|> ignore(string(":"))
|> integer(2)
|> optional(string("Z"))
defparsec :datetime, date |> ignore(string("T")) |> concat(time)
# parsec:MyParser
end
After running:
$ mix nimble_parsec.compile lib/my_parser.ex.exs
The following file will be generated:
# lib/my_parser.ex
defmodule MyParser do
@moduledoc false
def datetime(binary, opts \\ []) do
...
end
defp datetime__0(...) do
...
end
...
end
The file will be automatically formatted if using Elixir v1.6+.
## Options
* `-o` - configures the output location. Defaults to the input
file without its last extension
"""
use Mix.Task
@impl true
def run(args) do
Mix.Task.reenable("nimble_parsec.compile")
{opts, files} = OptionParser.parse!(args, strict: [output: :string], aliases: [o: :output])
Mix.Task.run("compile")
case files do
[file] -> compile(file, opts)
_ -> Mix.raise("Expected a single file to be given to nimble_parsec.compile")
end
end
defp compile(input, opts) do
output = opts[:output] || Path.rootname(input)
Mix.shell().info("Generating #{output}")
{:ok, _} = NimbleParsec.Recorder.start_link([])
try do
Code.compiler_options(ignore_module_conflict: true)
Code.require_file(input)
input
|> File.read!()
|> NimbleParsec.Recorder.replay(input)
|> write_to_disk(input, output)
after
Code.compiler_options(ignore_module_conflict: false)
NimbleParsec.Recorder.stop()
end
end
defp write_to_disk(contents, input, output) do
now = DateTime.utc_now() |> Map.put(:microsecond, {0, 0}) |> to_string
prelude = """
# Generated from #{input}, do not edit.
# Generated at #{now}.
"""
File.write!(output, [prelude | contents])
end
end