119 lines
2.6 KiB
Elixir
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
|