defmodule AOC do @moduledoc """ Advent of Code solution module macro and helpers. This module contains the `aoc/3` macro, which should be used to write a solution module for a given advent of code challenge. The intended use is to write your solution for day ``, year `` as follows: ``` import AOC aoc , do def p1 do # Part 1 solution goes here end def p2 do # Part 1 solution goes here end end ``` Defining a module with the `aoc/3` macro has a few advantages: - Helper functions to access the input (if it is present) are inserted into the generated module. - The `AOC.IEx` functions can be used to call your solutions in the module, making your life a bit easier. Overall, the `aoc/3` macro is intended to allow you to forego writing boilerplate code which is shared between all the solutions. Note that the code skeleton shown above can be generated by running `mix aoc.gen` or `mix aoc`. ## `aoc/3` and `use AOC` Internally, `aoc/3` generates a module with a predefined name (`Y.D`) which contains a `use AOC, day: , year: ` statement. In turn, the `__using__/1` macro defined in this module is responsible for generating helper functions. Thus, if you prefer to use a different naming scheme than the one imposed by `aoc/3`, you can write your module as follows: ``` defmodule MySolution do use AOC, day: , year: ... end ``` When the `AOC` module is used like this, the helper functions defined below are still usable, but the helpers in `AOC.IEx` will not work. ## Helper functions This module defines various functions such as `input_path/2`, `input_string/2` and `input_stream/2`. Inside the generated module, helpers are inserted which call these functions with the module's day / year. Thus, if you call `input_path()` inside your solution module, it will call `input_path/2` for you with the module's day and year, obtaining the path to the appropriate input file. The following table provides an overview of the inserted functions and their counterparts in this module: | Generated | Calls | |------------------|------------------| | `input_path/0` | `input_path/2` | | `input_string/0` | `input_string/2` | | `input_stream/0` | `input_stream/2` | The generated functions are overridable, i.e. you can define your own version of these functions which will overwrite the generated function. This is handy to do something like the following: ``` def input_stream, do: super() |> Stream.map(&String.to_integer/1) ``` """ alias AOC.Helpers @doc """ Generate an advent of code solution module for a given year and day. The generated module will be named `Y.D`. `use AOC` will be injected in the body of the module, so that the input helpers described in the module documentation are available. ## Examples ``` import AOC aoc 2020, 1 do def some_function do :foo end end ``` is equivalent to: ``` defmodule Y2020.D1 do use AOC def some_function do :foo end end ``` """ defmacro aoc(year, day, do: body) do quote do defmodule unquote(Helpers.module_name(year, day)) do use unquote(__MODULE__), year: unquote(year), day: unquote(day) unquote(body) end end end defmacro __using__(opts) do day = Keyword.fetch!(opts, :day) year = Keyword.fetch!(opts, :year) quote do def input_path, do: unquote(__MODULE__).input_path(unquote(year), unquote(day)) def input_string, do: unquote(__MODULE__).input_string(unquote(year), unquote(day)) def input_stream, do: unquote(__MODULE__).input_stream(unquote(year), unquote(day)) defoverridable input_path: 0 defoverridable input_stream: 0 defoverridable input_string: 0 end end @doc """ Get the input path for `year`, `day`. Obtains the path where `mix aoc.input` stores the input for `year`, `day`. This path defaults to `input/_.txt`, but can be customized. Please refer to the `mix aoc.get` documentation for more information. """ def input_path(year, day), do: Helpers.input_path(year, day) @doc """ Get the input contents of `year`, `day`. Obtained by calling `File.read!/1` on the path returned by `input_path/2`. """ def input_string(year, day), do: File.read!(input_path(year, day)) @doc """ Stream the contents of the input for `year`, `day`. The stream is created by calling `File.stream!/1` on the path returned by `input_path/2`. Afterwards, `String.trim/1` is mapped over the stream (using `Stream.map/2`), to remove trailing newlines and whitespace. """ def input_stream(year, day) do input_path(year, day) |> File.stream!() |> Stream.map(&String.trim/1) end end