defmodule Mix.Tasks.Aoc.Run do alias AoC.CLI use Mix.Task @shortdoc "Runs the solution for a puzzle" @command CLI.part_command(__MODULE__, benchmark: [ type: :boolean, default: false, short: :b, doc: "Runs the solution repeatedly for 5 seconds to print statistics about execution time." ] ) @moduledoc """ Runs your solution with the corresponding year/day input from `priv/inputs`. #{CLI.format_usage(@command, format: :moduledoc)} """ def run(argv) do CLI.compile() CLI.init_env() %{options: options} = CLI.parse_or_halt!(argv, @command) %{year: year, day: day, part: part} = CLI.validate_options!(options) case AoC.Input.ensure_local(year, day) do {:ok, _} -> :ok {:error, reason} -> CLI.halt_error(inspect(reason)) end CLI.writeln([ "Solution for ", CLI.color(:yellow, to_string(year)), " day ", CLI.color(:yellow, to_string(day)) ]) part = translate_part(part) run_print(year, day, part) if options.benchmark do benchmark(year, day, [part]) end end defp translate_part(n) do case n do 1 -> :part_one 2 -> :part_two nil -> nil end end defp run_print(year, day, nil) do run_print(year, day, :part_one) run_print(year, day, :part_two) end defp run_print(year, day, part) do {usecs, result} = :timer.tc(fn -> run(year, day, part) end) print_result(result, usecs, year, day, part) end defp run(year, day, part) do AoC.run(year, day, part) rescue e -> {:error, {:run_error, e, __STACKTRACE__}} catch t, e -> {:error, {:run_error, {t, e}, __STACKTRACE__}} end defp print_result({:ok, result}, usecs, _year, _day, part) do Mix.Shell.IO.info([ "#{part}: ", IO.ANSI.cyan(), IO.ANSI.bright(), inspect(result, charlists: :as_lists, pretty: true), IO.ANSI.normal(), IO.ANSI.default_color(), " in #{format_time(usecs)}" ]) end defp print_result({:error, :not_implemented}, _usecs, year, day, part) do Mix.Shell.IO.info([ IO.ANSI.yellow(), "#{part}: #{inspect(AoC.Mod.module_name(year, day))}.#{part}/1 is not implemented", IO.ANSI.default_color() ]) end defp print_result({:error, {:run_error, e, stack}}, _usecs, year, day, part) do CLI.error( "#{part}: #{inspect(AoC.Mod.module_name(year, day))}.#{part}/1 error: #{run_error_message(e, stack)}" ) end defp run_error_message(%{__exception__: true} = e, stack) do Exception.format(:error, e, stack) end defp run_error_message({kind, e}, stack) when kind in [:error, :throw, :exit] do Exception.format_banner(kind, e, stack) end defp benchmark(year, day, [nil]) do benchmark(year, day, [:part_one, :part_two]) end defp benchmark(year, day, parts) do parts = Enum.filter(parts, &match?({:ok, _}, run(year, day, &1))) benchables = Enum.map(parts, fn part -> {part, fn -> AoC.run(year, day, part) end} end) case benchables do [] -> CLI.warn("No part to benchmark") _ -> Benchee.run(Map.new(benchables), warmup: 0, time: 5, memory_time: 0, print: %{ benchmarking: true, configuration: false, fast_warning: false } ) end end defp format_time(time) do [IO.ANSI.bright(), do_format_time(time), IO.ANSI.normal()] end defp do_format_time(time) when time > 999_999 do "#{Float.round(time / 1_000_000, 2)}s" end defp do_format_time(time) when time > 999 do "#{Float.round(time / 1000, 2)}ms" end defp do_format_time(time) do "#{time}µs" end end