Andrew Fontaine

Mostly code

Advent of Code 2019: Day 3.5

04 Dec 2019

After 3 puzzles, I am taking a look at how things are set up, and making a few tweaks.


My projections just do not work. It gets confused when trying to call :A on a test file, going to or making a source file instead of jumping to the appropriate puzzle file. To alleviate this, I needed to make some alterations:

diff --git a/.projections.json b/.projections.json
index 46d04f5..1ad0e62 100644
--- a/.projections.json
+++ b/.projections.json
@@ -1,29 +1,38 @@
-  "lib/*.ex": {
-    "alternate": "test/{dirname}/{basename}_test.exs",
-    "type": "source",
+  "lib/mix/tasks/*.ex": {
+    "type": "task",
     "template": [
-      "defmodule {camelcase|capitalize|dot} do",
+      "defmodule Mix.Tasks.{camelcase|capitalize|dot} do",
+      "  use Mix.Task",
+      "  ",
-  "lib/*.exs": {
-    "alternate": "test/{dirname}/{basename}_test.exs",
+  "lib/Y*.ex": {
+    "alternate": "test/Y{dirname}/{basename}_test.exs",
     "type": "puzzle",
     "template": [
       "import AdventOfCode",
       "aoc {dirname}, {basename} do",
-      "  def p1(), do: nil",
+      "  def p1(), do: :not_solved",
-      "  def p2(), do: nil",
+      "  def p2(), do: :not_solved",
-  "test/*_test.exs": {
-    "alternate": "lib/{dirname}/{basename}.ex",
+  "lib/Y**/utils/*.ex": {
+    "alternate": "test/Y{dirname}/utils/{basename}_test.exs",
+    "type": "source",
+    "template": [
+      "defmodule AdventOfCode.Y{dirname}.{camelcase|capitalize|dot} do",
+      "end"
+    ]
+  },
+  "test/Y*_test.exs": {
+    "alternate": "lib/Y{dirname}/{basename}.ex",
     "type": "test",
     "template": [
       "defmodule {camelcase|capitalize|dot}Test do",

First, I added a new file type! task is to easily jump to or spin up any new mix tasks that I think I might need, which I will talk about below. Secondly, I moved all the source files to a utils directory. This is to keep the ability to :Esource 2020/new_file next year, while also fixing jumping from a test to a puzzle (jumping from a test to a source still works, too). Next, I updated puzzle to include the Y in the path name, so instead of jumping to Y2019/2, I can jump to 2019/2. I also updated the template so the functions return :not_solved (for the new mix task below). test was set up fine, no problems 💯.

Mix tasks

As I mentioned previously, I wanted a mix task that would run and print a given solution. My friends, it is HERE!

defmodule Mix.Tasks.Solution do
  use Mix.Task

  def run(args) when args == [] do
    date = get_date()
    run([, date.year])

  def run([d] = args) when length(args) == 1 do
    run([d, get_date().year])

  def run([d, y | _]) do
    if Code.ensure_loaded?(AdventOfCode.module_name(y, d)) do
      if function_exported?(AdventOfCode.module_name(y, d), :p1, 0) do"The solution for Y#{y}, D#{d}, P1 is #{AdventOfCode.p1(d, y)}")

      if function_exported?(AdventOfCode.module_name(y, d), :p2, 0) do"The solution for Y#{y}, D#{d}, P2 is #{AdventOfCode.p2(d, y)}")
    else"The solution Y#{y}, D#{d} is not available")

  defp get_date() do
    :ok = Application.ensure_started(:tzdata)

    case"America/Toronto", Tzdata.TimeZoneDatabase) do
      {:ok, d} -> d
      _ -> Date.utc_today()

This task allows me to run mix solution [d [y]] to run the solutions for a given day and year. It is smart enough to check if a solution function exists by calling function_exported?/3 with my generated module name, and :p1 or :p2 for each puzzle, and nicely printing the results. If the module doesn’t exist, an error message is printed instead. I think this task needs some more work to clean it up, and maybe have a different message if the result is :not_solved, but it is good for now.

I ❤ feedback. Let me know what you think of this article on Twitter @afontaine_ca