選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

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