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