From aaba05529e36433519067702f2da0c6e97f5ab5a Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Thu, 3 Dec 2020 19:03:37 -0600 Subject: [PATCH] Add input helper (#3) Co-authored-by: Mitchell Hanberg --- .gitignore | 2 + README.md | 30 ++++++++++++- config/config.exs | 16 +++++++ config/test.exs | 3 ++ lib/advent_of_code/input.ex | 87 +++++++++++++++++++++++++++++++++++++ mix.exs | 8 ++-- 6 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 config/test.exs create mode 100644 lib/advent_of_code/input.ex diff --git a/.gitignore b/.gitignore index 7bdbd47..6e95091 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index de6e3a9..5efa7a0 100644 --- 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 diff --git a/config/config.exs b/config/config.exs index d2d855e..4422bd5 100644 --- a/config/config.exs +++ b/config/config.exs @@ -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 index 0000000..e7693e4 --- /dev/null +++ b/config/test.exs @@ -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 index 0000000..0b2394c --- /dev/null +++ b/lib/advent_of_code/input.ex @@ -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 7cb9dd5..ccedd9a 100644 --- 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 -- 2.43.0