nimble_parsec.compile.ex (2667B)
1 defmodule Mix.Tasks.NimbleParsec.Compile do 2 @shortdoc "Compiles a parser and injects its content into the parser file" 3 4 @moduledoc ~S""" 5 Compiles a parser from a template. 6 7 mix nimble_parsec.compile template.ex.exs 8 9 This task is useful to generate parsers that have no runtime dependency 10 on NimbleParsec. 11 12 ## Examples 13 14 Let's define a template file: 15 16 # lib/my_parser.ex.exs 17 defmodule MyParser do 18 @moduledoc false 19 20 # parsec:MyParser 21 import NimbleParsec 22 23 date = 24 integer(4) 25 |> ignore(string("-")) 26 |> integer(2) 27 |> ignore(string("-")) 28 |> integer(2) 29 30 time = 31 integer(2) 32 |> ignore(string(":")) 33 |> integer(2) 34 |> ignore(string(":")) 35 |> integer(2) 36 |> optional(string("Z")) 37 38 defparsec :datetime, date |> ignore(string("T")) |> concat(time) 39 40 # parsec:MyParser 41 end 42 43 After running: 44 45 mix nimble_parsec.compile lib/my_parser.ex.exs 46 47 The following file will be generated: 48 49 # lib/my_parser.ex 50 defmodule MyParser do 51 @moduledoc false 52 53 def datetime(binary, opts \\ []) do 54 ... 55 end 56 57 defp datetime__0(...) do 58 ... 59 end 60 61 ... 62 end 63 64 The file will be automatically formatted if using Elixir v1.6+. 65 66 ## Options 67 68 * `-o` - configures the output location. Defaults to the input 69 file without its last extension 70 71 """ 72 73 use Mix.Task 74 75 @impl true 76 def run(args) do 77 Mix.Task.reenable("nimble_parsec.compile") 78 {opts, files} = OptionParser.parse!(args, strict: [output: :string], aliases: [o: :output]) 79 Mix.Task.run("compile") 80 81 case files do 82 [file] -> compile(file, opts) 83 _ -> Mix.raise("Expected a single file to be given to nimble_parsec.compile") 84 end 85 end 86 87 defp compile(input, opts) do 88 output = opts[:output] || Path.rootname(input) 89 Mix.shell().info("Generating #{output}") 90 91 {:ok, _} = NimbleParsec.Recorder.start_link([]) 92 93 try do 94 Code.compiler_options(ignore_module_conflict: true) 95 Code.require_file(input) 96 97 input 98 |> File.read!() 99 |> NimbleParsec.Recorder.replay(input) 100 |> write_to_disk(input, output) 101 after 102 Code.compiler_options(ignore_module_conflict: false) 103 NimbleParsec.Recorder.stop() 104 end 105 end 106 107 defp write_to_disk(contents, input, output) do 108 now = DateTime.utc_now() |> Map.put(:microsecond, {0, 0}) |> to_string 109 110 prelude = """ 111 # Generated from #{input}, do not edit. 112 # Generated at #{now}. 113 114 """ 115 116 File.write!(output, [prelude | contents]) 117 end 118 end