Scripts/parking.scm

154 lines
5.9 KiB
Scheme
Raw Normal View History

2022-04-20 16:53:30 -04:00
;;;; A program to print out the current real-time status of the computer parking terminal equipped
;;;; parking garages in the city of Linköping using the municipal open data API.
2022-04-18 15:00:02 -04:00
(import http-client)
(import srfi-1)
(import (chicken io))
2022-04-18 16:30:07 -04:00
(import srfi-13)
2022-04-20 17:34:53 -04:00
(import srfi-14)
2022-04-20 17:55:29 -04:00
(import (chicken condition))
2022-05-14 07:47:52 -04:00
(import medea)
;; We want members to be parsed as lowercase symbols, and arrays to be parsed as lists.
;; This is not a legal way to parse json, which requires case-sensitive members
;; but makes for neater scheme code.
(json-parsers (append `((member . ,(lambda (k v)
(cons (string->symbol (string-downcase k)) v)))
(array . ,identity)
(null . ,(constantly '()))) (json-parsers)))
2022-04-18 15:00:02 -04:00
2022-05-14 07:50:43 -04:00
;; The resultant type definition for the above parser rules.
(define-type json *)
(define-type json
(or string boolean number float null
(list-of json)
(list-of (pair symbol json))))
2022-04-28 12:28:50 -04:00
(: curry (procedure * --> procedure))
2022-04-18 15:00:02 -04:00
(define (curry fn a)
(lambda (b)
(fn a b)))
2022-04-28 12:28:50 -04:00
(: curry2 (procedure * * --> procedure))
2022-04-19 12:12:48 -04:00
(define (curry2 fn a b)
(lambda (c)
(fn a b c)))
2022-04-28 12:28:50 -04:00
(: perhaps (procedure * -> *))
2022-04-18 15:00:02 -04:00
(define (perhaps fn a)
(if a (fn a) a))
2022-05-14 07:47:52 -04:00
(: upon (('a -> boolean) ('a -> 'b) 'a -> (or 'a 'b)))
2022-04-19 12:12:48 -04:00
(define (upon pred? fn a)
2022-04-28 12:28:50 -04:00
(if (pred? a) (fn a) a))
2022-04-19 12:12:48 -04:00
2022-04-28 12:28:50 -04:00
(: member? (* list --> boolean))
2022-04-18 15:00:02 -04:00
(define (member? a ln)
(if (member a ln) #t #f))
2022-04-28 12:28:50 -04:00
(: default ('a 'b --> (or 'a 'b)))
2022-04-18 15:00:02 -04:00
(define (default b a)
(if a a b))
2022-04-28 12:28:50 -04:00
(define-type association-list (list-of pair))
2022-04-20 16:53:30 -04:00
;;; Return the value for a given key in an association list, or #f if it doesn't exist.
2022-04-28 12:28:50 -04:00
(: value (* association-list --> *))
2022-04-18 15:00:02 -04:00
(define value
(compose (curry perhaps cdr) assoc))
2022-04-20 16:53:30 -04:00
;;; Return the value for a given key in an association list, or a default value if it doesn't exist.
2022-04-28 12:28:50 -04:00
(: value-with-default (* * association-list --> *))
2022-04-18 15:00:02 -04:00
(define (value-with-default k v ln)
(default v (value k ln)))
2022-04-28 12:28:50 -04:00
(: has-key? (* association-list --> boolean))
2022-04-18 15:00:02 -04:00
(define (has-key? k ln)
(member? k (map car ln)))
2022-04-28 12:28:50 -04:00
(: sort (('a 'a -> boolean) (list-of 'a) --> (list-of 'a)))
2022-04-20 12:09:35 -04:00
(define (sort less-than? ln)
(if (<= (length ln) 1)
ln
(append (sort less-than? (filter (complement (curry less-than? (car ln))) (cdr ln)))
(list (car ln))
(sort less-than? (filter (curry less-than? (car ln)) (cdr ln))))))
2022-04-20 16:53:30 -04:00
;;; Sort using fn to extract a value for less-than? to compare for each element in ln.
2022-04-28 12:28:50 -04:00
(: sort-via (('a 'a -> boolean) ('b -> 'a) (list-of 'b) -> (list-of 'b)))
2022-04-18 15:00:02 -04:00
(define (sort-via less-than? fn ln)
2022-04-20 12:09:35 -04:00
(sort (lambda (a b)
(less-than? (fn a) (fn b))) ln))
2022-04-18 15:00:02 -04:00
2022-04-28 12:28:50 -04:00
(: display-newline (string -> undefined))
2022-04-18 15:00:02 -04:00
(define (display-newline str)
(display str)
(newline))
2022-04-28 12:28:50 -04:00
(: ->string ((or string symbol number char) -> string))
2022-04-18 15:00:02 -04:00
(define (->string a)
(cond ((string? a) a)
2022-04-18 15:14:20 -04:00
((symbol? a) (symbol->string a))
((number? a) (number->string a))
((char? a) (string a))))
2022-04-20 16:53:30 -04:00
;;; Given a list of either symbols or strings and an association list:
;;; look up every symbol from left to right in the association list and replace it with the value found
2022-04-20 17:34:53 -04:00
;;; then concatenate the resulting list of strings.
;;; Inspired by Python 3's dictionary formatting string syntax.
2022-04-28 12:28:50 -04:00
(: format-assoc ((list-of (or symbol string)) association-list --> string))
2022-04-18 15:00:02 -04:00
(define (format-assoc statement ln)
(apply string-append
2022-04-19 12:12:48 -04:00
(map (curry2 upon symbol? (compose ->string (curry (flip value) ln))) statement)))
2022-04-18 15:00:02 -04:00
2022-05-14 07:47:52 -04:00
2022-04-20 16:53:30 -04:00
;;; Get data from a uri and parse it as json.
2022-05-14 07:47:52 -04:00
(: simple-json-request (string -> json))
2022-04-18 15:00:02 -04:00
(define (simple-json-request uri)
2022-04-20 17:55:29 -04:00
(condition-case (with-input-from-request uri #f read-json)
((exn i/o net) (begin (display-newline "Connection failed.")
(exit 1)))))
2022-04-18 15:00:02 -04:00
2022-04-20 16:53:30 -04:00
;;; The API key is an alphanumeric string of length 32
;;; The Linköping municipal open data API key generator portal is located at http://opendata.linkoping.se
2022-04-28 12:28:50 -04:00
(: parking-key string)
2022-04-18 15:00:02 -04:00
(define parking-key
2022-04-20 17:34:53 -04:00
(string-delete char-set:whitespace (with-input-from-file "key-parking" read-line)))
2022-04-18 15:00:02 -04:00
2022-04-20 16:53:30 -04:00
;;; Get a list of parking lots with various metadata from the municipal servers.
;;; It should be noted that this API is both glitchy and bugged. Two bugs have been noted:
;;; the timestamp parameter, which is supposed to give you a list of lots changed since a given UNIX timestamp
;;; instead results in giving you every lot up until a timestamp of under one hour into the future, after which
;;; it returns nothing. This bug may be platform related; as of this writing the server system providing the API
;;; runs on Windows Server 2016 via IIS 10.0.
;;; The second bug involves returning only partial data upon request - no pattern to this bug has been observed.
2022-05-14 07:47:52 -04:00
(: get-parking-lots (-> json))
2022-04-18 15:00:02 -04:00
(define (get-parking-lots)
2022-04-20 10:51:32 -04:00
(value 'parkingareanewlist
(simple-json-request (string-append "http://parkering.linkoping.se/Parkeringsdata/ParkeringsdataV1.svc/GetParkeringsYtaList/" parking-key "/0"))))
2022-04-18 15:00:02 -04:00
2022-04-20 16:53:30 -04:00
;;; Only lots tracked by parking lot computer terminals have the parkingspacesavailable key.
2022-05-14 07:47:52 -04:00
(: computerized (json --> boolean))
2022-04-18 15:00:02 -04:00
(define (computerized? lot)
2022-04-18 16:30:07 -04:00
(has-key? 'parkingspacesavailable lot))
2022-04-18 15:00:02 -04:00
2022-04-20 16:53:30 -04:00
;;; The handicap lots are tracked as separate lots with duplicate names in the data set.
2022-05-14 07:47:52 -04:00
(: only-has-handicap-spots? (json --> boolean))
2022-04-18 15:00:02 -04:00
(define (only-has-handicap-spots? lot)
2022-04-18 16:30:07 -04:00
(equal? '(4) (value 'parkingtypes lot)))
2022-04-18 15:00:02 -04:00
2022-04-20 16:53:30 -04:00
;;; Electric car lots have a separate lot type, and the downtown parking garages have spaces with charging stations
;;; but they are not implemented in the data set.
2022-05-14 07:47:52 -04:00
(: electric-car-lot? (json --> boolean))
2022-04-18 15:00:02 -04:00
(define (electric-car-lot? lot)
2022-04-18 16:30:07 -04:00
(member? 3 (value 'parkingtypes lot)))
2022-04-18 15:00:02 -04:00
2022-05-14 07:47:52 -04:00
(: main (-> undefined))
2022-04-18 15:00:02 -04:00
(define (main)
(display-newline "Parking spaces available:")
2022-04-18 17:34:08 -04:00
(map (compose display-newline (curry format-assoc '(name ": " parkingspacesavailable "/" parkingspaces)))
2022-04-20 16:53:30 -04:00
(sort-via string-ci<? (curry value 'name)
2022-04-20 12:09:35 -04:00
(filter (conjoin computerized? (complement only-has-handicap-spots?)) (get-parking-lots)))))
2022-04-18 15:00:02 -04:00
(main)