From 89ded18386cc42f07adaa6df3eb180bb1c25f74c Mon Sep 17 00:00:00 2001 From: bluepython508 Date: Fri, 1 Dec 2023 17:51:59 +0000 Subject: [PATCH] Infra --- .envrc | 2 + .formatter.exs | 4 ++ .gitignore | 32 ++++++++++ README.md | 21 +++++++ flake.lock | 39 ++++++++++++ flake.nix | 7 +++ lib/aoc2023.ex | 13 ++++ lib/common.ex | 2 + lib/day1.ex | 15 +++++ lib/mix_tasks.ex | 155 +++++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 25 ++++++++ mix.lock | 11 ++++ shell.nix | 24 ++++++++ 13 files changed, 350 insertions(+) create mode 100644 .envrc create mode 100644 .formatter.exs create mode 100644 .gitignore create mode 100644 README.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 lib/aoc2023.ex create mode 100644 lib/common.ex create mode 100644 lib/day1.ex create mode 100644 lib/mix_tasks.ex create mode 100644 mix.exs create mode 100644 mix.lock create mode 100644 shell.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..716ced9 --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +use flake +export AOC_SESSION=$(cat session) diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9c35c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +aoc2023-*.tar + +# Temporary files, for example, from tests. +/tmp/ +.direnv +/.nix-mix +/.nix-hex + +/inputs +session diff --git a/README.md b/README.md new file mode 100644 index 0000000..58285fa --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Aoc2023 + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `aoc2023` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:aoc2023, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d17d48b --- /dev/null +++ b/flake.lock @@ -0,0 +1,39 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1700794826, + "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=", + "path": "/nix/store/6h04ab83y72nm16hh9g3llgg0s296ynl-source", + "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "id": "systems", + "type": "indirect" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..56db765 --- /dev/null +++ b/flake.nix @@ -0,0 +1,7 @@ +{ + outputs = { self, nixpkgs, systems }: { + devShells = nixpkgs.lib.genAttrs (import systems) (system: { + default = nixpkgs.legacyPackages.${system}.beam.packages.erlang_26.callPackage ./shell.nix {}; + }); + }; +} diff --git a/lib/aoc2023.ex b/lib/aoc2023.ex new file mode 100644 index 0000000..5e55da4 --- /dev/null +++ b/lib/aoc2023.ex @@ -0,0 +1,13 @@ +defmodule Aoc2023 do + defmacro __using__([]) do + quote do + import Aoc2023.Common + + def main(input) do + parsed = parse(input) + IO.puts("Part 1: #{part1(parsed)}") + IO.puts("Part 2: #{part2(parsed)}") + end + end + end +end diff --git a/lib/common.ex b/lib/common.ex new file mode 100644 index 0000000..830237f --- /dev/null +++ b/lib/common.ex @@ -0,0 +1,2 @@ +defmodule Aoc2023.Common do +end diff --git a/lib/day1.ex b/lib/day1.ex new file mode 100644 index 0000000..5b9a9f9 --- /dev/null +++ b/lib/day1.ex @@ -0,0 +1,15 @@ +defmodule Aoc2023.Day1 do + use Aoc2023 + + def parse(input) do + input + end + + def part1(input) do + nil + end + + def part2(input) do + nil + end +end diff --git a/lib/mix_tasks.ex b/lib/mix_tasks.ex new file mode 100644 index 0000000..a3380b9 --- /dev/null +++ b/lib/mix_tasks.ex @@ -0,0 +1,155 @@ +defmodule Mix.Tasks.Aoc do + use Mix.Task + + defp module(1), do: Aoc2023.Day1 + # [MODULE INSERTION POINT] + + defp base_dir(), do: System.get_env("AOC_BASE") + + defp tests(day) do + File.ls!(tests_dir(day)) |> Enum.filter(&(not String.contains?(&1, "."))) + end + + defp read(path) do + f = File.open!(path, [:utf8]) + contents = IO.read(f, :eof) + :ok = File.close(f) + 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 + input + end + def part2(input) do + input + 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(empty_fallback: fn -> 1 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"]) 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) + + if p1e do + if to_string(p1) != p1e do + IO.puts("Failed at #{test}.1: expected #{p1e}, got:") + dbg(p1) + end + else + IO.puts("Test #{test}.1") + dbg(p1) + end + + p2 = mod.part2(parsed) + + if p2e do + if to_string(p2) != p2e do + IO.puts("Failed at #{test}.2: expected #{p2e}, got:") + dbg(p2) + end + else + IO.puts("Test #{test}.2") + dbg(p2) + end + 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 +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..dbe42ef --- /dev/null +++ b/mix.exs @@ -0,0 +1,25 @@ +defmodule Aoc2023.MixProject do + use Mix.Project + + def project do + [ + app: :aoc2023, + version: "0.1.0", + elixir: "~> 1.15", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + def application do + [ + extra_applications: [:logger] + ] + end + + defp deps do + [ + {:httpoison, "~> 2.0"} + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..14e8515 --- /dev/null +++ b/mix.lock @@ -0,0 +1,11 @@ +%{ + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, + "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..7419c10 --- /dev/null +++ b/shell.nix @@ -0,0 +1,24 @@ +{ + lib, + pkgs, + mkShell, + elixir, + elixir-ls, + inotify-tools, +}: +mkShell { + packages = + [elixir elixir-ls] + ++ lib.lists.optional (pkgs.system == "x86_64-linux") inotify-tools + ++ lib.lists.optionals (pkgs.system == "aarch64-darwin") (with pkgs.darwin.apple_sdk.frameworks; [ + CoreFoundation + CoreServices + ]); + shellHook = '' + mkdir -p .nix-mix + mkdir -p .nix-hex + export MIX_HOME=$PWD/.nix-mix + export HEX_HOME=$PWD/.nix-hex + export AOC_BASE=$PWD + ''; +}