]> git.njae.me.uk Git - advent-of-code-24-elixir.git/commitdiff
Add input helper (#3)
authorDaniel Flanagan <daniel@lyte.dev>
Fri, 4 Dec 2020 01:03:37 +0000 (19:03 -0600)
committerMitchell Hanberg <mitch@mitchellhanberg.com>
Fri, 4 Dec 2020 01:31:49 +0000 (20:31 -0500)
Co-authored-by: Mitchell Hanberg <mitch@mitchellhanberg.com>
.gitignore
README.md
config/config.exs
config/test.exs [new file with mode: 0644]
lib/advent_of_code/input.ex [new file with mode: 0644]
mix.exs

index 7bdbd47c592cb079c57dec73d1135d13d3a67fe3..6e95091921b6fa24c510148a48922533ae394194 100644 (file)
@@ -22,3 +22,5 @@ erl_crash.dump
 # Ignore package tarball (built via "mix hex.build").
 advent_of_code_2018-*.tar
 
+# Ignore configuration secrets
+/config/secrets.exs
index de6e3a910cc5fc10936336508f43b3cb20cdc056..5efa7a05a7f759f110b97c099c164439fa791876 100644 (file)
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ defmodule Mix.Tasks.D01.P1 do
 
   @shortdoc "Day 01 Part 1"
   def run(args) do
-    input = nil
+    input = AdventOfCode.Input.get!(2020, 1)
 
     if Enum.member?(args, "-b"),
       do: Benchee.run(%{part_1: fn -> input |> part1() end}),
@@ -65,6 +65,34 @@ defmodule Mix.Tasks.D01.P1 do
 end
 ```
 
+### Optional Automatic Input Retriever
+
+This starter comes with a module that will automatically get your inputs so you
+don't have to mess with copy/pasting. Don't worry, it automatically caches your
+inputs to your machine so you don't have to worry about slamming the Advent of
+Code server. You will need to configure it with your cookie and make sure to
+enable it. You can do this by creating a `config/secrets.exs` file containing
+the following:
+
+```elixir
+config :advent_of_code, AdventOfCode.Input,
+  allow_network?: true,
+  session_cookie: "..." # yours will be longer
+```
+
+After which, you can retrieve your inputs using the module:
+
+```elixir
+day = 1
+year = 2020
+AdventOfCode.Input.get!(day, year)
+# or just have it auto-detect the current year
+AdventOfCode.Input.get!(7)
+# and if your input somehow gets mangled and you need a fresh one:
+AdventOfCode.Input.delete!(7, 2019)
+# and the next time you `get!` it will download a fresh one -- use this sparingly!
+```
+
 ## Installation
 
 ```bash
index d2d855e6d032fe60873bb1a15b885e624bb3a33a..4422bd5885a69833faed3f7002a09e8bd7434b3e 100644 (file)
@@ -1 +1,17 @@
 use Mix.Config
+
+config :advent_of_code, AdventOfCode.Input,
+  # allow_network?: true,
+  session_cookie: System.get_env("ADVENT_OF_CODE_SESSION_COOKIE")
+
+# If you don't like environment variables, put your cookie in
+# a `config/secrets.exs` file like this:
+#
+# config :advent_of_code, AdventOfCode.Input,
+#   advent_of_code_session_cookie: "session=..."
+
+try do
+  import_config "secrets.exs"
+rescue
+  _ -> :ok
+end
diff --git a/config/test.exs b/config/test.exs
new file mode 100644 (file)
index 0000000..e7693e4
--- /dev/null
@@ -0,0 +1,3 @@
+# Don't let CI fetch inputs from the server! Be nice!
+# https://www.reddit.com/r/adventofcode/comments/5h6mmt/how_to_read_input/day6jlw
+config :advent_of_code, AdventOfCode.Input, allow_network?: false
diff --git a/lib/advent_of_code/input.ex b/lib/advent_of_code/input.ex
new file mode 100644 (file)
index 0000000..0b2394c
--- /dev/null
@@ -0,0 +1,87 @@
+defmodule AdventOfCode.Input do
+  @moduledoc """
+  This module can help with automatically managing your Advent of Code input
+  files. It will retrieve them once from the server and cache them to your
+  machine.
+
+  By default, it is configured to have network requests disabled. You can
+  easily turn it on by editing the configuration.
+  """
+
+  @doc """
+  Retrieves the specified input for your account. If the input is not in your
+  cache, it will be retrieved from the server if `allow_network?: true` is
+  configured and your cookie is setup.
+  """
+  def get!(day, year \\ nil)
+  def get!(day, nil), do: get!(day, default_year())
+
+  def get!(day, year) do
+    cond do
+      in_cache?(day, year) ->
+        from_cache!(day, year)
+
+      allow_network?() ->
+        download!(day, year)
+
+      true ->
+        raise "Cache miss for day #{day} of year #{year} and `:allow_network?` is not `true`"
+    end
+  end
+
+  @doc """
+  If, somehow, your input is invalid or mangled and you want to delete it from
+  your cache so you can re-fetch it, this will save your bacon.
+  Please don't use this to retrieve the input from the server repeatedly!
+  """
+  def delete!(day, year \\ nil)
+  def delete!(day, nil), do: delete!(day, default_year())
+  def delete!(day, year), do: File.rm!(cache_path(day, year))
+
+  defp cache_path(day, year), do: Path.join(cache_dir(), "/#{year}/#{day}.aocinput")
+  defp in_cache?(day, year), do: File.exists?(cache_path(day, year))
+
+  defp store_in_cache!(day, year, input) do
+    path = cache_path(day, year)
+    :ok = path |> Path.dirname() |> File.mkdir_p()
+    :ok = File.write(path, input)
+  end
+
+  defp from_cache!(day, year), do: File.read!(cache_path(day, year))
+
+  defp download!(day, year) do
+    {:ok, {{'HTTP/1.1', 200, 'OK'}, _, input}} =
+      :httpc.request(
+        :get,
+        {'https://adventofcode.com/#{year}/day/#{day}/input', headers()},
+        [],
+        []
+      )
+
+    store_in_cache!(day, year, input)
+
+    to_string(input)
+  end
+
+  defp cache_dir do
+    config()
+    |> Keyword.get(
+      :cache_dir,
+      Path.join([System.get_env("XDG_CACHE_HOME", "~/.cache"), "/advent_of_code_inputs"])
+    )
+    |> Path.expand()
+  end
+
+  defp default_year do
+    case :calendar.local_time() do
+      {{y, 12, _}, _} -> y
+      {{y, _, _}, _} -> y - 1
+    end
+  end
+
+  defp config, do: Application.get_env(:advent_of_code, __MODULE__)
+  defp allow_network?, do: Keyword.get(config(), :allow_network?, false)
+
+  defp headers,
+    do: [{'cookie', String.to_charlist("session=" <> Keyword.get(config(), :session_cookie))}]
+end
diff --git a/mix.exs b/mix.exs
index 7cb9dd5b40a49a63fd2eecc4cea9306fadfb3f4a..ccedd9afd456b92ef66899f7f0f75f0e81a9abd7 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -1,11 +1,11 @@
-defmodule AdventOfCode2019.MixProject do
+defmodule AdventOfCode.MixProject do
   use Mix.Project
 
   def project do
     [
-      app: :advent_of_code_2019,
+      app: :advent_of_code,
       version: "0.1.0",
-      elixir: "~> 1.7",
+      elixir: "~> 1.9",
       start_permanent: Mix.env() == :prod,
       deps: deps()
     ]
@@ -14,7 +14,7 @@ defmodule AdventOfCode2019.MixProject do
   # Run "mix help compile.app" to learn about applications.
   def application do
     [
-      extra_applications: [:logger]
+      extra_applications: [:logger, :inets]
     ]
   end