diff --git a/.gitignore b/.gitignore
index fd301fc..22b06e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
/obj/
*.exe
/inputs/
+.env
diff --git a/advent-of-code-2024.csproj b/advent-of-code-2024.csproj
index 32483ca..9c37257 100644
--- a/advent-of-code-2024.csproj
+++ b/advent-of-code-2024.csproj
@@ -23,16 +23,6 @@
1701;1702;8981
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Logger.cs b/src/Logger.cs
index 1181d7a..031a50a 100644
--- a/src/Logger.cs
+++ b/src/Logger.cs
@@ -50,12 +50,24 @@ internal class Logger
Debug.WriteLine(StripColorCodes(msg));
}
+ public static void LogErrorLine(string msg)
+ {
+ Console.Error.WriteLine(InsertColorCodes(msg));
+ Debug.WriteLine(StripColorCodes(msg));
+ }
+
public static void Log(string msg)
{
Console.Write(InsertColorCodes(msg));
Debug.Write(StripColorCodes(msg));
}
+ public static void LogError(string msg)
+ {
+ Console.Error.Write(InsertColorCodes(msg));
+ Debug.Write(StripColorCodes(msg));
+ }
+
private static string InsertColorCodes(string msg)
{
foreach (var code in colorCodes)
diff --git a/src/Util/DotEnv.cs b/src/Util/DotEnv.cs
new file mode 100644
index 0000000..35b6285
--- /dev/null
+++ b/src/Util/DotEnv.cs
@@ -0,0 +1,65 @@
+namespace aoc2024.Util;
+
+internal static class DotEnv
+{
+ internal static string GetDotEnvContents(string fromPath = "")
+ {
+ if (string.IsNullOrEmpty(fromPath))
+ {
+ fromPath = Directory.GetCurrentDirectory();
+ }
+
+ try
+ {
+ var dir = new DirectoryInfo(fromPath);
+ while (dir != null)
+ {
+ var dotEnv = Path.Combine(dir.FullName, ".env");
+ if (File.Exists(dotEnv))
+ {
+ return dotEnv;
+ }
+
+ dir = dir.Parent;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"Exception searching for .env path from {fromPath}: {ex}");
+ }
+
+ return "";
+ }
+
+ internal static bool SetEnvironment(string fromPath = "")
+ {
+ var dotEnv = GetDotEnvContents(fromPath);
+ if (string.IsNullOrEmpty(dotEnv))
+ {
+ return false;
+ }
+
+ var lines = File.ReadAllLines(dotEnv);
+ for (int i = 0; i < lines.Length; i++)
+ {
+ var line = lines[i].Trim();
+
+ if (string.IsNullOrWhiteSpace(line) || line.StartsWith('#'))
+ {
+ continue;
+ }
+
+ var parts = line.Split('=');
+ if (parts.Length != 2)
+ {
+ Logger.LogErrorLine($"DotEnv file {dotEnv} line {i + 1} does not match expected `key=value` format. Line: {line}");
+ continue;
+ }
+
+ System.Diagnostics.Debug.WriteLine($"Setting environment variable `{parts[0]}` = `{parts[1]}`");
+ Environment.SetEnvironmentVariable(parts[0], parts[1]);
+ }
+
+ return true;
+ }
+}
diff --git a/src/Util/Parsing.cs b/src/Util/Parsing.cs
index d47c0f8..12dd0ca 100644
--- a/src/Util/Parsing.cs
+++ b/src/Util/Parsing.cs
@@ -44,7 +44,7 @@ public static class Parsing
}
}
- var filename = $"inputs/{inputName}.txt";
+ var filename = Path.Combine("inputs", $"{inputName}.txt");
if (File.Exists(filename))
{
if (Directory.Exists(Path.GetDirectoryName(filename)!) && File.Exists(filename))
@@ -63,7 +63,14 @@ public static class Parsing
// accessible at runtime. instead, we assume Logger is also part of the "default namespace"
var resourceName = $"{typeof(Logger).Namespace}.inputs.{inputName}.txt";
using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
- using StreamReader reader = new(stream!);
+ if (stream == null)
+ {
+ Logger.LogErrorLine($"Unable to find file or resource matching requested input {inputName}.");
+ Logger.LogErrorLine("Do you have a .env file with an AOC_SESSION=... line containing your session cookie?");
+ return;
+ }
+
+ using StreamReader reader = new(stream);
while (reader.ReadLine() is { } readLine)
{
processor(readLine);
diff --git a/src/Util/RetrieveInput.cs b/src/Util/RetrieveInput.cs
new file mode 100644
index 0000000..4c1e44c
--- /dev/null
+++ b/src/Util/RetrieveInput.cs
@@ -0,0 +1,19 @@
+using System.Net;
+
+namespace aoc2024.Util;
+
+internal static class RetrieveInput
+{
+ internal static async Task Get(string session, string year, int day)
+ {
+ var cookies = new CookieContainer();
+ cookies.Add(new Uri("https://adventofcode.com"), new Cookie("session", session));
+
+ using var handler = new HttpClientHandler() { CookieContainer = cookies };
+ using var client = new HttpClient(handler);
+
+ var inputContents = await client.GetStringAsync($"https://adventofcode.com/{year}/day/{day}/input");
+ Directory.CreateDirectory("inputs");
+ File.WriteAllText(Path.Combine("inputs", $"{day.ToString().PadLeft(2, '0')}.txt"), inputContents);
+ }
+}
diff --git a/src/main.cs b/src/main.cs
index 38bddcb..d64fe15 100644
--- a/src/main.cs
+++ b/src/main.cs
@@ -39,40 +39,88 @@ if (runPart1 != null || runPart2 != null)
if (runAll)
{
+ desiredDays.Clear();
foreach (var type in types)
{
- using var day = (Day)Activator.CreateInstance(type)!;
- day.Go(runPart1 ?? true, runPart2 ?? true);
+ desiredDays.Add(type.Name[^2..]);
+ }
+}
+
+if (desiredDays.Count == 0)
+{
+ desiredDays.Add("");
+}
+
+var getDayNumFromArg = (string arg) =>
+{
+ if (string.IsNullOrEmpty(arg))
+ {
+ arg = types.Last().ToString()[^2..];
+ }
+
+ return int.Parse(arg);
+};
+
+var getDayInstanceFromArg = (string arg) =>
+{
+ var num = getDayNumFromArg(arg);
+ var typ = types.FirstOrDefault(x => x.Name == $"Day{num.ToString().PadLeft(2, '0')}");
+ if (typ == null)
+ {
+ return null;
+ }
+
+ var day = (Day?) Activator.CreateInstance(typ);
+ return day;
+};
+
+aoc2024.Util.DotEnv.SetEnvironment();
+var sessionVal = Environment.GetEnvironmentVariable("AOC_SESSION");
+if (!string.IsNullOrEmpty(sessionVal))
+{
+ var year = Environment.GetEnvironmentVariable("AOC_YEAR");
+ if (string.IsNullOrEmpty(year))
+ {
+ // was going to use the current year, but this solution is specifically designed for a certain year, so using that makes more sense.
+ // don't forget your find/replace when copying this code for a new aoc year!
+ //year = DateTime.Now.Year.ToString();
+ year = "2024";
+ System.Diagnostics.Debug.WriteLine($"No AOC_YEAR env var defined or set in .env file, so assuming year {year}");
+ }
+
+ foreach (var day in desiredDays)
+ {
+ var dayNum = getDayNumFromArg(day);
+ if (!File.Exists(Path.Combine("inputs", $"{dayNum.ToString().PadLeft(2, '0')}.txt")))
+ {
+ Logger.Log($"Downloading input for day {dayNum}...");
+ try
+ {
+ await aoc2024.Util.RetrieveInput.Get(sessionVal, year, dayNum);
+ Logger.LogLine("done!");
+ }
+ catch (Exception ex)
+ {
+ Logger.LogLine($"failed! {ex}");
+ }
+
+ Logger.LogLine("");
+ }
}
}
else
{
- if (desiredDays.Count == 0)
- {
- desiredDays.Add("");
- }
-
- foreach (var desiredDay in desiredDays)
- {
- Day? day = null;
- if (string.IsNullOrEmpty(desiredDay))
- {
- day = (Day) Activator.CreateInstance(types.Last())!;
- }
- else
- {
- var type = types.FirstOrDefault(x => x.Name == $"Day{desiredDay.PadLeft(2, '0')}");
- if (type == null)
- {
- Logger.LogLine($"Unknown day {desiredDay}");
- }
- else
- {
- day = (Day?) Activator.CreateInstance(type);
- }
- }
-
- day?.Go(runPart1 ?? true, runPart2 ?? true);
- day?.Dispose();
- }
+ System.Diagnostics.Debug.WriteLine("No AOC_SESSION env var defined or set in .env file, so automatic input downloading not available.");
+}
+
+foreach (var desiredDay in desiredDays)
+{
+ Day? day = getDayInstanceFromArg(desiredDay);
+ if (day == null)
+ {
+ Logger.LogLine($"Unknown day {desiredDay}");
+ }
+
+ day?.Go(runPart1 ?? true, runPart2 ?? true);
+ day?.Dispose();
}