Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

152 строки
5.8KB

  1. ;;;; A program to print out the current real-time status of the computer parking terminal equipped
  2. ;;;; parking garages in the city of Linköping using the municipal open data API.
  3. (import http-client)
  4. (import srfi-1)
  5. (import (chicken io))
  6. (import srfi-13)
  7. (import srfi-14)
  8. (import (chicken condition))
  9. (import medea)
  10. ;; We want members to be parsed as lowercase symbols, and arrays to be parsed as lists.
  11. ;; This is not a legal way to parse json, which requires case-sensitive members
  12. ;; but makes for neater scheme code.
  13. (json-parsers (append `((member . ,(lambda (k v)
  14. (cons (string->symbol (string-downcase k)) v)))
  15. (array . ,identity)
  16. (null . ,(constantly '()))) (json-parsers)))
  17. (: curry (procedure * --> procedure))
  18. (define (curry fn a)
  19. (lambda (b)
  20. (fn a b)))
  21. (: curry2 (procedure * * --> procedure))
  22. (define (curry2 fn a b)
  23. (lambda (c)
  24. (fn a b c)))
  25. (: perhaps (procedure * -> *))
  26. (define (perhaps fn a)
  27. (if a (fn a) a))
  28. (: upon (('a -> boolean) ('a -> 'b) 'a -> (or 'a 'b)))
  29. (define (upon pred? fn a)
  30. (if (pred? a) (fn a) a))
  31. (: member? (* list --> boolean))
  32. (define (member? a ln)
  33. (if (member a ln) #t #f))
  34. (: default ('a 'b --> (or 'a 'b)))
  35. (define (default b a)
  36. (if a a b))
  37. (define-type association-list (list-of pair))
  38. ;;; Return the value for a given key in an association list, or #f if it doesn't exist.
  39. (: value (* association-list --> *))
  40. (define value
  41. (compose (curry perhaps cdr) assoc))
  42. ;;; Return the value for a given key in an association list, or a default value if it doesn't exist.
  43. (: value-with-default (* * association-list --> *))
  44. (define (value-with-default k v ln)
  45. (default v (value k ln)))
  46. (: has-key? (* association-list --> boolean))
  47. (define (has-key? k ln)
  48. (member? k (map car ln)))
  49. (: sort (('a 'a -> boolean) (list-of 'a) --> (list-of 'a)))
  50. (define (sort less-than? ln)
  51. (if (<= (length ln) 1)
  52. ln
  53. (append (sort less-than? (filter (complement (curry less-than? (car ln))) (cdr ln)))
  54. (list (car ln))
  55. (sort less-than? (filter (curry less-than? (car ln)) (cdr ln))))))
  56. ;;; Sort using fn to extract a value for less-than? to compare for each element in ln.
  57. (: sort-via (('a 'a -> boolean) ('b -> 'a) (list-of 'b) -> (list-of 'b)))
  58. (define (sort-via less-than? fn ln)
  59. (sort (lambda (a b)
  60. (less-than? (fn a) (fn b))) ln))
  61. (: display-newline (string -> undefined))
  62. (define (display-newline str)
  63. (display str)
  64. (newline))
  65. (: ->string ((or string symbol number char) -> string))
  66. (define (->string a)
  67. (cond ((string? a) a)
  68. ((symbol? a) (symbol->string a))
  69. ((number? a) (number->string a))
  70. ((char? a) (string a))))
  71. ;;; Given a list of either symbols or strings and an association list:
  72. ;;; look up every symbol from left to right in the association list and replace it with the value found
  73. ;;; then concatenate the resulting list of strings.
  74. ;;; Inspired by Python 3's dictionary formatting string syntax.
  75. (: format-assoc ((list-of (or symbol string)) association-list --> string))
  76. (define (format-assoc statement ln)
  77. (apply string-append
  78. (map (curry2 upon symbol? (compose ->string (curry (flip value) ln))) statement)))
  79. (define-type json *)
  80. (define-type json
  81. (or string boolean number float null
  82. (list-of json)
  83. (list-of (pair symbol json))))
  84. ;;; Get data from a uri and parse it as json.
  85. (: simple-json-request (string -> json))
  86. (define (simple-json-request uri)
  87. (condition-case (with-input-from-request uri #f read-json)
  88. ((exn i/o net) (begin (display-newline "Connection failed.")
  89. (exit 1)))))
  90. ;;; The API key is an alphanumeric string of length 32
  91. ;;; The Linköping municipal open data API key generator portal is located at http://opendata.linkoping.se
  92. (: parking-key string)
  93. (define parking-key
  94. (string-delete char-set:whitespace (with-input-from-file "key-parking" read-line)))
  95. ;;; Get a list of parking lots with various metadata from the municipal servers.
  96. ;;; It should be noted that this API is both glitchy and bugged. Two bugs have been noted:
  97. ;;; the timestamp parameter, which is supposed to give you a list of lots changed since a given UNIX timestamp
  98. ;;; instead results in giving you every lot up until a timestamp of under one hour into the future, after which
  99. ;;; it returns nothing. This bug may be platform related; as of this writing the server system providing the API
  100. ;;; runs on Windows Server 2016 via IIS 10.0.
  101. ;;; The second bug involves returning only partial data upon request - no pattern to this bug has been observed.
  102. (: get-parking-lots (-> json))
  103. (define (get-parking-lots)
  104. (value 'parkingareanewlist
  105. (simple-json-request (string-append "http://parkering.linkoping.se/Parkeringsdata/ParkeringsdataV1.svc/GetParkeringsYtaList/" parking-key "/0"))))
  106. ;;; Only lots tracked by parking lot computer terminals have the parkingspacesavailable key.
  107. (: computerized (json --> boolean))
  108. (define (computerized? lot)
  109. (has-key? 'parkingspacesavailable lot))
  110. ;;; The handicap lots are tracked as separate lots with duplicate names in the data set.
  111. (: only-has-handicap-spots? (json --> boolean))
  112. (define (only-has-handicap-spots? lot)
  113. (equal? '(4) (value 'parkingtypes lot)))
  114. ;;; Electric car lots have a separate lot type, and the downtown parking garages have spaces with charging stations
  115. ;;; but they are not implemented in the data set.
  116. (: electric-car-lot? (json --> boolean))
  117. (define (electric-car-lot? lot)
  118. (member? 3 (value 'parkingtypes lot)))
  119. (: main (-> undefined))
  120. (define (main)
  121. (display-newline "Parking spaces available:")
  122. (map (compose display-newline (curry format-assoc '(name ": " parkingspacesavailable "/" parkingspaces)))
  123. (sort-via string-ci<? (curry value 'name)
  124. (filter (conjoin computerized? (complement only-has-handicap-spots?)) (get-parking-lots)))))
  125. (main)