#lang racket (require (prefix-in net: "net.rkt") (prefix-in gmi: "gmi.rkt")) (require net/url-string) ;; global state variable which will be an input port containing the ;; rendered out gemtext document as it shall be shown to the user (define document-buffer null) ;; global state which will be the document structure, before rendering (define document null) ;; global state for the url of the currently visited document (define current-url null) (define temporary-failures '((40 . "temporary failure") (41 . "server unavailable") (42 . "CGI error") (43 . "proxy error") (44 . "slow down"))) (define permanent-failures '((50 . "permanent failure") (51 . "not found") (52 . "gone") (53 . "proxy request refused") (59 . "bad request"))) (define (get url-str) (define (iter url-str depth) (let-values ([(status header c-in) (net:request url-str)]) ;; TODO there are bunch of other status codes to deal with for ;; compliance (cond ;; clients MUST reject status codes outside of the 10-69 range [(or (< status 10) (> status 69)) (displayln "WARNING: server returned invalid status code")] ;; 10-19 INPUT REQUIRED [(and (>= status 10) (<= status 19)) (display "input requested > ") (get (string-append url-str "?" (read-line)))] ;; 20-29 SUCCESS [(and (>= status 20) (<= status 29)) (let-values ([(doc) (gmi:parse (port->lines c-in))] [(db-in db-out) (make-pipe #f)]) (set! document doc) (set! document-buffer db-in) (parameterize ([current-output-port db-out]) (gmi:render doc)) (set! current-url url-str) (let ([remaining (pipe-content-length db-in)]) (printf "document retrieved. ~a bytes\n" remaining)) (next-cmd))] ;; 30-39 REDIRECT [(and (>= status 30) (<= status 39)) (if (> depth 5) (displayln "WARNING: maximum redirection depth exceeded") (iter header (sub1 depth)))] ;; 40-49 TEMPORARY FAILURE [(and (>= status 40) (<= status 49)) (printf "status ~a: ~a\n" status (dict-ref temporary-failures status "temporary failure"))] ;; 50-59 PERMANENT FAILURE [(and (>= status 50) (<= status 59)) (printf "status ~a: ~a\n" status (dict-ref permanent-failures status "permanent failure"))] ;; 60-69 CERTIFICATE REQUIRED [(and (>= status 60) (<= status 69)) (displayln "certificate handling not yet implemented")]))) (iter url-str 5)) (define (go-cmd url) (if (non-empty-string? url) (let () (when (not (string-contains? url "://")) (set! url (string-append "gemini://" url))) (get url)) (displayln "go where?"))) (define (next-cmd) (define (iter depth) (when (and (> depth 0) (> (pipe-content-length document-buffer) 0)) (let () (displayln (read-line document-buffer)) (iter (sub1 depth))))) (iter 10) (newline) (let ([remaining (pipe-content-length document-buffer)]) (printf "~a bytes remaining\n" remaining))) (define (visit-cmd line) (define url (gmi:match-link document (string->number line))) (set! url (url->string (combine-url/relative (string->url current-url) url))) (get url)) (define (repl) (display "G-300 > ") (let ([matches (regexp-match #px"(\\w+)\\s*(.*)" (read-line))]) (cond ;; next command. also default [(or (not matches) (string=? (cadr matches) "next") (string=? (cadr matches) "n")) (next-cmd)] ;; go command [(or (string=? (cadr matches) "go") (string=? (cadr matches) "g")) (go-cmd (caddr matches))] ;; visit link command [(andmap char-numeric? (string->list (cadr matches))) (visit-cmd (cadr matches))])) (repl))