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(); }