diff --git a/.envrc b/.envrc deleted file mode 100644 index ed1e8be..0000000 --- a/.envrc +++ /dev/null @@ -1,3 +0,0 @@ -watch_file flake.nix flake.lock - -use flake . --no-pure-eval diff --git a/2025/cmd/generate/cmd.go b/2025/cmd/generate/cmd.go new file mode 100644 index 0000000..1145924 --- /dev/null +++ b/2025/cmd/generate/cmd.go @@ -0,0 +1,49 @@ +package generate + +import ( + "fmt" + "log" + "os" + "strconv" + + "github.com/spf13/cobra" +) + +func handleErr(err error, folder string) { + os.RemoveAll(folder) + log.Fatal(err) +} + +var GenerateCmd = &cobra.Command{ + Use: "generate", + Short: "Generate a new day directory", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + day, err := strconv.Atoi(args[0]) + if err != nil { + log.Fatal(err) + } + + folder := fmt.Sprintf("./day%02d", day) + if err := os.Mkdir(folder, 0755); err != nil { + log.Fatal(err) + } + + if err := generateMainCode(day); err != nil { + handleErr(err, folder) + } + + if err := generateTestCode(day); err != nil { + handleErr(err, folder) + } + + if err := retrieveInput(day); err != nil { + handleErr(err, folder) + } + + filename := fmt.Sprintf("./day%02d/test.txt", day) + if err := os.WriteFile(filename, []byte{}, 0755); err != nil { + handleErr(err, folder) + } + }, +} diff --git a/2025/cmd/generate/fetch_input.go b/2025/cmd/generate/fetch_input.go new file mode 100644 index 0000000..3cd1852 --- /dev/null +++ b/2025/cmd/generate/fetch_input.go @@ -0,0 +1,92 @@ +package generate + +import ( + "fmt" + "io" + "net/http" + "net/http/cookiejar" + "net/url" + "os" +) + +const ( + baseURL = "https://adventofcode.com/2025/day/%d/input" + host = "adventofcode.com" + userAgent = "github.com/C4theBomb/advent-of-code by c4patino@gmail.com" + cookieEnvName = "COOKIE" +) + +func getCookie() (string, error) { + cookieValue := os.Getenv(cookieEnvName) + if cookieValue == "" { + return "", fmt.Errorf("%s environment variable not set", cookieEnvName) + } + + return cookieValue, nil + +} + +func createHTTPClient(cookieValue string) (*http.Client, error) { + jar, err := cookiejar.New(nil) + if err != nil { + return nil, fmt.Errorf("failed to create cookie jar: %w", err) + } + + domainURL := &url.URL{Scheme: "https", Host: host} + + client := &http.Client{Jar: jar} + jar.SetCookies(domainURL, []*http.Cookie{{Name: "session", Value: cookieValue}}) + + return client, nil +} + +func fetchInput(client *http.Client, day int) ([]byte, error) { + url := fmt.Sprintf(baseURL, day) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("User-Agent", userAgent) + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(body)) + } + + return body, nil +} + +func retrieveInput(day int) error { + cookieValue, err := getCookie() + if err != nil { + return err + } + + client, err := createHTTPClient(cookieValue) + if err != nil { + return err + } + + body, err := fetchInput(client, day) + if err != nil { + return err + } + + filename := fmt.Sprintf("./day%02d/input.txt", day) + if err := os.WriteFile(filename, []byte(body), 0755); err != nil { + return err + } + + return nil +} diff --git a/2025/cmd/generate/main_template.go b/2025/cmd/generate/main_template.go new file mode 100644 index 0000000..70b87e4 --- /dev/null +++ b/2025/cmd/generate/main_template.go @@ -0,0 +1,66 @@ +package generate + +import ( + "fmt" + "os" + "os/exec" +) + +const mainCodeTemplate = `package day%02d + +import ( + "bufio" + "fmt" + "os" +) + + +func Part1() int { + return -1 +} + +func Part2() int { + return -1 +} + +func Run(filename string) (any, any) { + file, err := os.Open(filename) + if err != nil { + panic(err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + + fmt.Println(line) + } + + if err := scanner.Err(); err != nil { + panic(err) + } + + part1 := Part1() + part2 := Part2() + + return part1, part2 +} +` + +func generateMainCode(day int) error { + testCode := fmt.Sprintf(mainCodeTemplate, day) + + filename := fmt.Sprintf("./day%02d/day%02d.go", day, day) + + if err := os.WriteFile(filename, []byte(testCode), 0755); err != nil { + return err + } + + cmd := exec.Command("go", "fmt", filename) + if err := cmd.Run(); err != nil { + return err + } + + return nil +} diff --git a/2025/cmd/generate/test_template.go b/2025/cmd/generate/test_template.go new file mode 100644 index 0000000..3d9020e --- /dev/null +++ b/2025/cmd/generate/test_template.go @@ -0,0 +1,60 @@ +package generate + +import ( + "fmt" + "os" + "os/exec" +) + +const testCodeTemplate = `package day%02d + +import ( + "os" + "testing" +) + +type TestCase struct { + FileName string + Part1 any + Part2 any +} + +func TestDay%02d(t *testing.T) { + tests := []TestCase{ + {"test.txt", -1, -1}, + // {"input.txt", -1, -1}, + } + + for _, test := range tests { + if _, err := os.Stat(test.FileName); os.IsNotExist(err) { + t.Fatalf("test file does not exist: %%v", test.FileName) + } + + part1, part2 := Run(test.FileName) + if test.Part1 != nil && part1 != test.Part1 { + t.Fatalf("%%s: unexpected part1:\nwant:\t%%v\ngot:\t%%v", test.FileName, test.Part1, part1) + } + + if test.Part2 != nil && part2 != test.Part2 { + t.Fatalf("%%s: unexpected part2:\nwant:\t%%v\ngot:\t%%v", test.FileName, test.Part2, part2) + } + } +} +` + +func generateTestCode(day int) error { + testCode := fmt.Sprintf(testCodeTemplate, day, day) + + filename := fmt.Sprintf("./day%02d/day%02d_test.go", day, day) + + if err := os.WriteFile(filename, []byte(testCode), 0755); err != nil { + return err + } + + cmd := exec.Command("go", "fmt", filename) + if err := cmd.Run(); err != nil { + return err + } + + return nil +} diff --git a/2025/cmd/root.go b/2025/cmd/root.go new file mode 100644 index 0000000..77d6f87 --- /dev/null +++ b/2025/cmd/root.go @@ -0,0 +1,61 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "strconv" + + "cpatino.com/advent-of-code/2025/cmd/generate" + "github.com/spf13/cobra" +) + +var days = map[int]func(string) (any, any){} + +var rootCmd = &cobra.Command{ + Use: "advent-of-code", + Short: "Advent of Code 2024", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + day, err := strconv.Atoi(args[0]) + if err != nil { + log.Fatal(err) + } + + file, _ := cmd.Flags().GetString("file") + if file == "" { + file = fmt.Sprintf("./day%02d/input.txt", day) + } + + if _, err := os.Stat(file); os.IsNotExist(err) { + log.Fatalf("file does not exist: %s", file) + } + + log.Printf("Day: %d, Input File: %s\n", day, file) + + part1, part2 := days[day](file) + + log.Printf("Part 1: %v\n", part1) + log.Printf("Part 2: %v\n", part2) + }, +} + +func init() { + rootCmd.AddCommand(generate.GenerateCmd) + rootCmd.Flags().String("file", "", "Path to a file") +} + +func Execute() error { + if len(os.Args) == 1 { + return rootCmd.Execute() + } + + subCmd := os.Args[1] + for _, cmd := range rootCmd.Commands() { + if cmd.Use == subCmd { + return cmd.Execute() + } + } + + return rootCmd.Execute() +} diff --git a/2025/cmd/update/main.go b/2025/cmd/update/main.go new file mode 100644 index 0000000..dda2e91 --- /dev/null +++ b/2025/cmd/update/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" +) + +func main() { + const mainTemplate = `package cmd + +import ( + "fmt" + "log" + "os" + "strconv" + + "github.com/spf13/cobra" + "cpatino.com/advent-of-code/2025/cmd/generate" + + %s +) + +var days = map[int]func(string) (any, any){ + %s +} + +var rootCmd = &cobra.Command{ + Use: "advent-of-code", + Short: "Advent of Code 2024", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + day, err := strconv.Atoi(args[0]) + if err != nil { + log.Fatal(err) + } + + file, _ := cmd.Flags().GetString("file") + if file == "" { + file = fmt.Sprintf("./day%%02d/input.txt", day) + } + + if _, err := os.Stat(file); os.IsNotExist(err) { + log.Fatalf("file does not exist: %%s", file) + } + + log.Printf("Day: %%d, Input File: %%s\n", day, file) + + part1, part2 := days[day](file) + + log.Printf("Part 1: %%v\n", part1) + log.Printf("Part 2: %%v\n", part2) + }, +} + +func init() { + rootCmd.AddCommand(generate.GenerateCmd) + rootCmd.Flags().String("file", "", "Path to a file") +} + +func Execute() error { + if len(os.Args) == 1 { + return rootCmd.Execute() + } + + subCmd := os.Args[1] + for _, cmd := range rootCmd.Commands() { + if cmd.Use == subCmd { + return cmd.Execute() + } + } + + return rootCmd.Execute() +} +` + + var imports, mappings strings.Builder + + err := filepath.Walk("./", func(path string, info os.FileInfo, err error) error { + if !info.IsDir() || !strings.HasPrefix(info.Name(), "day") { + return nil + } + + dayStr := strings.TrimPrefix(info.Name(), "day") + + day, err := strconv.Atoi(dayStr) + if err != nil { + log.Fatal(err) + } + + imports.WriteString(fmt.Sprintf("\t\"cpatino.com/advent-of-code/2025/day%02d\"\n", day)) + mappings.WriteString(fmt.Sprintf("\t%d: day%02d.Run,\n", day, day)) + + return nil + }) + + importsStr := strings.TrimSpace(imports.String()) + mappingsStr := strings.TrimSpace(mappings.String()) + + if err != nil { + fmt.Println("Error walking the path:", err) + os.Exit(1) + } + + mainCode := fmt.Sprintf(mainTemplate, importsStr, mappingsStr) + + os.WriteFile("./cmd/root.go", []byte(mainCode), 0644) + + cmd := exec.Command("go", "fmt", "./cmd/root.go") + if err := cmd.Run(); err != nil { + fmt.Println("Error running go fmt:", err) + os.Exit(1) + } +} diff --git a/2025/go.mod b/2025/go.mod new file mode 100644 index 0000000..0f96860 --- /dev/null +++ b/2025/go.mod @@ -0,0 +1,10 @@ +module cpatino.com/advent-of-code/2025 + +go 1.23.3 + +require github.com/spf13/cobra v1.8.1 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect +) diff --git a/2025/go.sum b/2025/go.sum new file mode 100644 index 0000000..912390a --- /dev/null +++ b/2025/go.sum @@ -0,0 +1,10 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/2025/main.go b/2025/main.go new file mode 100644 index 0000000..3e1fd17 --- /dev/null +++ b/2025/main.go @@ -0,0 +1,14 @@ +//go:generate go run ./cmd/update +package main + +import ( + "log" + + "cpatino.com/advent-of-code/2025/cmd" +) + +func main() { + log.SetFlags(0) + + cmd.Execute() +}