diff --git a/.gitignore b/.gitignore index 9e42951..b54e9d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .envrc /inputs/* !/inputs/.gitkeep +/.status.rktd diff --git a/scripts/aoc-lib.rkt b/scripts/aoc-lib.rkt index e0532e2..27482d6 100644 --- a/scripts/aoc-lib.rkt +++ b/scripts/aoc-lib.rkt @@ -1,19 +1,26 @@ #lang racket +;; library to interact with the AoC API and track solutions progress + (require net/uri-codec net/http-client) -(provide aoc-fetch-input aoc-fetch-challenge aoc-submit-answer) +(provide aoc-fetch-input aoc-fetch-challenge aoc-submit-answer + aoc-complete? aoc-set-complete!) (define *host* "adventofcode.com") +(define *status-file* ".status.rktd") +;; generates API paths (define/contract (puzzle-path year day endpoint) (-> string? string? (or/c "input" "answer" false/c) path?) (define base (build-path "/" year "day" day)) (if endpoint (build-path base endpoint) base)) +;; sets up necessary headers for API (define (make-headers session) (list (string-append "Cookie: session=" session) "Content-Type: application/x-www-form-urlencoded")) +;; http request helper (define (aoc-request year day endpoint session [method 'GET] [data #f]) (define (parse-headers hlist) (for/list ([h (in-list hlist)]) @@ -43,10 +50,12 @@ (port->bytes content))])) (do-request (path->string (puzzle-path year day endpoint)) (make-headers session) method data)) +;; gets the input file for a challenge (define/contract (aoc-fetch-input year day session) (-> string? string? string? input-port?) (aoc-request year day "input" session)) +;; submits an answer to the server (define/contract (aoc-submit-answer year day session part answer) (-> string? string? string? (or/c 1 2 "1" "2") string? (or/c symbol? bytes?)) (define data `((level . ,(~a part)) @@ -55,11 +64,41 @@ (port->bytes (aoc-request year day "answer" session 'POST (alist->form-urlencoded data)))) (match resp - [(pregexp #px"That's the right answer") 'answer-correct] + [(pregexp #px"That's the right answer") + (aoc-set-complete! day part) + 'answer-correct] [(pregexp #px"That's not the right answer") 'answer-incorrect] - [(pregexp #px"Did you already complete it?") 'already-completed] + [(pregexp #px"Did you already complete it?") + (aoc-set-complete! day part) + 'already-completed] [x x])) +;; fetches the HTML page for a challenge (define/contract (aoc-fetch-challenge year day session) (-> string? string? string? input-port?) (aoc-request year day #f session)) + +;; helper to generate entries for the challenge status file +(define (day+part->key day part) + (when (string? day) + (set! day (string->number day))) + (when (string? part) + (set! part (string->number part))) + (cons day part)) + +;; get the challenge status file +(define (aoc-get-status) + (cond [(file-exists? *status-file*) + (call-with-input-file *status-file* read)] + [else '()])) + +;; is a challenge complete already? +(define (aoc-complete? day part) + (set-member? (aoc-get-status) (day+part->key day part))) + +;; mark a challenge as completed +(define (aoc-set-complete! day part) + (define status (set-add (aoc-get-status) (day+part->key day part))) + (call-with-output-file + *status-file* (lambda (out) (write status out)) + #:mode 'binary #:exists 'replace)) diff --git a/scripts/aoc.rkt b/scripts/aoc.rkt new file mode 100644 index 0000000..1bd3bc4 --- /dev/null +++ b/scripts/aoc.rkt @@ -0,0 +1,25 @@ +#lang racket + +;; utilities for every challenge script + +(require "aoc-lib.rkt") +(provide answer dbg) + +;; in-expression debug print, uwu +(define (dbg x) + (pretty-write x) + x) + +;; submit a solution to the server if not already submitted +(define (answer day part answer) + (printf "answer ~a.~a: ~s\n" day part answer) + (unless (aoc-complete? day part) + (printf "submit? [Y/n]: ") + (match (string-downcase (string-trim (read-line))) + [(or "" "y" "yes") + (printf "submitting...\n") + (define resp + (aoc-submit-answer (getenv "AOC_YEAR") (~a day) (getenv "AOC_SESSION") (~a part) + (~a answer))) + (printf "server responded: ~a\n" resp)] + [_ (printf "not submitting\n")]))) diff --git a/scripts/make-day b/scripts/make-day index 187fa40..89df439 100755 --- a/scripts/make-day +++ b/scripts/make-day @@ -2,7 +2,8 @@ #lang racket (require racket/runtime-path - scribble/text (rename-in scribble/text/output [output scribble-output])) + scribble/text (rename-in scribble/text/output [output scribble-output]) + "aoc-lib.rkt") (define-runtime-path template "template.rktrkt") @@ -17,11 +18,14 @@ (eval `(include/text (file ,file))))) (scribble-output output-exp port)) - (command-line #:program "make-day" #:args (day) + ;; make solution file (call-with-output-file (format "~a.rkt" day) (lambda (out) - (eval-template (path->string template) (hash 'day day) out)))) + (eval-template (path->string template) (hash 'day day) out))) + ;; get input + (define in (aoc-fetch-input (getenv "AOC_YEAR") day (getenv "AOC_SESSION"))) + (call-with-output-file (build-path "inputs" day) (lambda (out) (copy-port in out)))) diff --git a/scripts/template.rktrkt b/scripts/template.rktrkt index d41469e..943054d 100644 --- a/scripts/template.rktrkt +++ b/scripts/template.rktrkt @@ -1,20 +1,30 @@ #lang racket +(require "scripts/aoc.rkt") + ;; solution for day @day ;; helper functions here +(define (part1 input) + ;; ... + (void)) + +(define (part2 input) + ;; ... + (void)) + (module+ test (require rackunit) ;; tests here - (void)) + (displayln "no tests :(")) (module+ main (define input (file->... "inputs/@day")) ;; part 1 - ;; ... + (answer @day 1 (part1 input)) ;; part 2 - ;; ... + (answer @day 2 (part2 input)) (displayln "meow"))