205 lines
4.8 KiB
Elixir
205 lines
4.8 KiB
Elixir
defmodule Mix.Tasks.Aoc do
|
|
use Mix.Task
|
|
import Aoc2023.Common
|
|
|
|
defp module(1), do: Aoc2023.Day1
|
|
defp module(2), do: Aoc2023.Day2
|
|
# [MODULE INSERTION POINT]
|
|
|
|
defp base_dir(), do: System.get_env("AOC_BASE")
|
|
|
|
defp tests(day) do
|
|
case File.ls(tests_dir(day)) do
|
|
{:ok, paths} ->
|
|
paths |> Enum.filter(&(not String.contains?(&1, ".")))
|
|
|
|
{:error, e} ->
|
|
dbg(e)
|
|
[]
|
|
end
|
|
end
|
|
|
|
defp read(path) do
|
|
f = File.open!(path, [:utf8])
|
|
contents = IO.read(f, :eof)
|
|
:ok = File.close(f)
|
|
String.trim(contents)
|
|
end
|
|
|
|
defp test(day, test) do
|
|
f = "#{tests_dir(day)}#{test}"
|
|
|
|
{
|
|
test,
|
|
read(f),
|
|
if File.exists?(f <> ".1") do
|
|
read(f <> ".1")
|
|
else
|
|
nil
|
|
end,
|
|
if File.exists?(f <> ".2") do
|
|
read(f <> ".2")
|
|
else
|
|
nil
|
|
end
|
|
}
|
|
end
|
|
|
|
defp tests_dir(day), do: "#{base_dir()}/tests/day#{day}/"
|
|
|
|
defp create_file(path, contents) do
|
|
File.mkdir_p!(Path.dirname(path))
|
|
f = File.open!(path, [:exclusive, :utf8])
|
|
IO.write(f, contents)
|
|
:ok = File.close(f)
|
|
end
|
|
|
|
def run(params) do
|
|
{opts, params} = params |> OptionParser.parse!(strict: [day: :integer])
|
|
day = opts |> Keyword.get(:day, Date.utc_today().day)
|
|
run(day, params)
|
|
end
|
|
|
|
defp run(day, ["new"]) do
|
|
create_file("#{base_dir()}/lib/day#{day}.ex", """
|
|
defmodule Aoc2023.Day#{day} do
|
|
use Aoc2023
|
|
|
|
def parse(input) do
|
|
input
|
|
end
|
|
|
|
def part1(_input) do
|
|
end
|
|
|
|
def part2(_input) do
|
|
end
|
|
end
|
|
""")
|
|
|
|
this_file = "#{base_dir()}/lib/mix_tasks.ex"
|
|
|
|
contents =
|
|
read(this_file)
|
|
|> String.replace(
|
|
"\n # [MODULE INSERTION POINT]",
|
|
"\n defp module(#{day}), do: Aoc2023.Day#{day}\n # [MODULE INSERTION POINT]"
|
|
)
|
|
|
|
f = File.open!(this_file, [:write, :utf8])
|
|
IO.write(f, contents)
|
|
:ok = File.close(f)
|
|
end
|
|
|
|
defp run(day, ["test", "new"]) do
|
|
last = tests(day) |> Enum.map(&String.to_integer(&1)) |> Enum.max(&>=/2, fn -> 0 end)
|
|
create_file("#{tests_dir(day)}#{last + 1}", IO.read(:stdio, :eof))
|
|
end
|
|
|
|
defp run(day, ["test", "expected", test, part]) do
|
|
create_file("#{tests_dir(day)}#{test}.#{part}", IO.read(:stdio, :eof))
|
|
end
|
|
|
|
defp run(day, ["test", "watch"]) do
|
|
{:ok, pid} = Mix.Tasks.Aoc.Watcher.start_link({[dirs: ["lib/", "test/day#{day}/"]], self()})
|
|
Process.monitor(pid)
|
|
loop = fn (loop) ->
|
|
IO.puts(IO.ANSI.clear() <> IO.ANSI.cursor(0, 0))
|
|
IO.puts "At #{DateTime.utc_now(:second)}:"
|
|
System.shell("mix compile")
|
|
:code.purge(module(day))
|
|
:code.purge(Aoc2023.Common)
|
|
:code.load_file(module(day))
|
|
run(day, ["test"])
|
|
receive do
|
|
:update -> loop.(loop)
|
|
{:DOWN, _, :process, ^pid, _} -> nil
|
|
end
|
|
end
|
|
loop.(loop)
|
|
end
|
|
|
|
defp run(day, ["test"]) do
|
|
mod = module(day)
|
|
tests = tests(day)
|
|
|
|
tests
|
|
|> Enum.map(&test(day, &1))
|
|
|> Enum.map(fn {test, input, p1e, p2e} ->
|
|
parsed = mod.parse(input)
|
|
p1 = mod.part1(parsed)
|
|
p2 = mod.part2(parsed)
|
|
|
|
result = fn (part, got, expected) ->
|
|
if expected do
|
|
if stringify(got) != expected do
|
|
IO.puts(IO.ANSI.format [:red, "Test #{test}.#{part} failed: expected #{expected}"])
|
|
dbg(got)
|
|
else
|
|
IO.puts(IO.ANSI.format [:green, "Test #{test}.#{part} succeeded"])
|
|
end
|
|
else
|
|
IO.puts("Test #{test}.#{part}:")
|
|
dbg(got)
|
|
end
|
|
end
|
|
|
|
result.(1, p1, p1e)
|
|
result.(2, p2, p2e)
|
|
end)
|
|
end
|
|
|
|
defp run(day, ["run"]) do
|
|
run(day, ["fetch"])
|
|
run_file(day, "#{base_dir()}/inputs/day#{day}")
|
|
end
|
|
|
|
defp run(day, ["run", "-"]) do
|
|
run_file(day)
|
|
end
|
|
|
|
defp run(day, ["fetch"]) do
|
|
file = "#{base_dir()}/inputs/day#{day}"
|
|
|
|
if not File.exists?(file) do
|
|
HTTPoison.start()
|
|
|
|
resp =
|
|
HTTPoison.get!("https://adventofcode.com/2023/day/#{day}/input",
|
|
"user-agent": "aoc-ex by ben@soroos.net",
|
|
cookie: "session=#{System.get_env("AOC_SESSION")}"
|
|
)
|
|
|
|
create_file(file, resp.body)
|
|
end
|
|
end
|
|
|
|
defp run_file(day, file \\ nil) do
|
|
module(day).main(if file, do: read(file), else: IO.read(:stdio, :eof))
|
|
end
|
|
|
|
|
|
defmodule Watcher do
|
|
use GenServer
|
|
|
|
def start_link(args) do
|
|
GenServer.start_link(__MODULE__, args)
|
|
end
|
|
|
|
def init({args, callback}) do
|
|
{:ok, watcher_pid} = FileSystem.start_link(args)
|
|
FileSystem.subscribe(watcher_pid)
|
|
{:ok, %{watcher_pid: watcher_pid, callback: callback}}
|
|
end
|
|
|
|
def handle_info({:file_event, watcher_pid, {_path, _events}}, %{watcher_pid: watcher_pid, callback: callback} = state) do
|
|
send(callback, :update)
|
|
{:noreply, state}
|
|
end
|
|
|
|
def handle_info({:file_event, _watcher_pid, :stop}, state) do
|
|
{:noreply, state}
|
|
end
|
|
end
|
|
end
|