From 270da290548b31fe8af9cfa8a4b60297165c9291 Mon Sep 17 00:00:00 2001 From: xenia Date: Mon, 18 Sep 2023 23:39:16 -0400 Subject: [PATCH] bike shaving :3 --- .envrc | 1 + scripts/.gitignore | 1 + scripts/writefreely-cli | 111 +++++++++++++++++++++++++++++++++++ scripts/writefreely-validate | 39 ++++++++++++ 4 files changed, 152 insertions(+) create mode 100644 .envrc create mode 100644 scripts/.gitignore create mode 100755 scripts/writefreely-cli create mode 100755 scripts/writefreely-validate diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..f9d77ee --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +PATH_add scripts diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 0000000..8dadff0 --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1 @@ +/secrets.rktd diff --git a/scripts/writefreely-cli b/scripts/writefreely-cli new file mode 100755 index 0000000..e2639b0 --- /dev/null +++ b/scripts/writefreely-cli @@ -0,0 +1,111 @@ +#!/usr/bin/env racket +#lang racket/base + +(require json + net/url + net/uri-codec + racket/cmdline + racket/file + racket/match + racket/port + racket/runtime-path + racket/system) + + +;; client configuration + +(struct app-config [instance token] #:prefab) + +(define (config-from-file filename) + (if (file-exists? filename) + (with-input-from-file filename read) + (app-config #f #f))) + +(define current-config (make-parameter #f)) + + +;; http request functions + +(define (make-header k v) + (string-append k ": " v)) + +(define (get-authorization) + (string-append "Token " (app-config-token (current-config)))) + +(define (make-api-url path #:query [query '()]) + (url "https" + #f (app-config-instance (current-config)) + #f #t + `(,(path/param "api" '()) + ,@(map (λ (x) (path/param x '())) path)) + query #f)) + +(define (api-req method path data #:authorization [auth? #t]) + (define-values (status headers rsp) + (http-sendrecv/url (make-api-url path) + #:method method + #:headers `(,@(if auth? + `(,(make-header "Authorization" (get-authorization))) + '()) + ,(make-header "Content-Type" "application/json")) + #:data (jsexpr->string data))) + (string->jsexpr (port->string rsp))) + + +;; writefreely api functions + +(define (login-user username password) + (api-req "POST" '("auth" "login") (hash 'alias username 'pass password))) + +(define (update-post id title content) + (api-req "POST" `("posts" ,id) (hash 'body content 'title title))) + + +;; script toplevel + +(module+ main + (define-runtime-path *default-config-file* "secrets.rktd") + + (define (read-password) + (system "stty -echo") + (begin0 + (read-line) + (system "stty echo") + (newline))) + + (current-config (config-from-file *default-config-file*)) + + (unless (and (app-config-instance (current-config)) (app-config-token (current-config))) + (printf "instance: ") + (define instance (read-line)) + (current-config (app-config instance #f)) + (printf "username: ") + (define username (read-line)) + (printf "password: ") + (define password (read-password)) + (define token + (match (login-user username password) + [(hash-table ('data (hash-table ('access_token token) _ ...)) _ ...) token] + [resp (error "failed to log in" resp)])) + (current-config (app-config instance token)) + (with-output-to-file *default-config-file* (λ () (write (current-config))))) + + (command-line + #:program "writefreely-cli" + #:args (post-id post-file) + + (define-values [title content] + (with-input-from-file post-file + (λ () + (values + (match (read-line) + [(pregexp #px"^# (.+)$" (list _ title)) title] + [_ (error "post has no title")]) + (port->string))))) + + (match (update-post post-id title content) + [(hash-table ('code 200) _ ...) + (printf "https://~a/posts/~a\n" (app-config-instance (current-config)) post-id)] + [resp (error "failed to post" resp)])) + + (void)) diff --git a/scripts/writefreely-validate b/scripts/writefreely-validate new file mode 100755 index 0000000..8b09462 --- /dev/null +++ b/scripts/writefreely-validate @@ -0,0 +1,39 @@ +#!/usr/bin/env racket +#lang racket/base + +(require json + racket/list + racket/port + racket/pretty) + +(define current-header (make-parameter #f)) + +(define (check-block blk) + (define c (hash-ref blk 'c)) + (define t (hash-ref blk 't)) + + (when (equal? t "Header") + (current-header (first (second c)))) + + (when (equal? t "CodeBlock") + (define content (second c)) + (for ([line (in-lines (open-input-string content))]) + (when (> (string-length line) 80) + (eprintf "WARNING: [#~a] found code line > 80\n" (current-header)))))) + +(define (check-any thing) + (when (list? thing) + (for-each check-any thing)) + (when (hash? thing) + (when (and (hash-has-key? thing 'c) (hash-has-key? thing 't)) + (check-block thing)) + (for-each check-any (hash-values thing)))) + +(define (check-ast doc-ast) + (check-any (hash-ref doc-ast 'blocks))) + +(module+ main + (define doc-ast (read-json)) + (current-header "") + (check-ast doc-ast) + (write-json doc-ast))