utils.lisp 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. (in-package #:chatikbot)
  2. (defvar *backoff-start* 1 "Initial back-off")
  3. (defvar *backoff-max* 64 "Maximum back-off delay")
  4. (defun loop-with-error-backoff (func)
  5. (let ((backoff *backoff-start*))
  6. (loop
  7. do
  8. (handler-case
  9. (progn
  10. (funcall func)
  11. (setf backoff *backoff-start*))
  12. (error (e)
  13. (log:error e)
  14. (log:info "Backing off for" backoff)
  15. (sleep backoff)
  16. (setf backoff (min *backoff-max*
  17. (* 2 backoff))))))))
  18. (defun replace-all (string part replacement &key (test #'char=))
  19. "Returns a new string in which all the occurences of the part
  20. is replaced with replacement."
  21. (with-output-to-string (out)
  22. (loop with part-length = (length part)
  23. for old-pos = 0 then (+ pos part-length)
  24. for pos = (search part string
  25. :start2 old-pos
  26. :test test)
  27. do (write-string string out
  28. :start old-pos
  29. :end (or pos (length string)))
  30. when pos do (write-string replacement out)
  31. while pos)))
  32. (defmacro aget (key alist)
  33. `(cdr (assoc ,key ,alist :test #'equal)))
  34. (defun mappend (fn &rest lists)
  35. "Apply fn to each element of lists and append the results."
  36. (apply #'append (apply #'mapcar fn lists)))
  37. (defun random-elt (choices)
  38. "Choose an element from a list at random."
  39. (elt choices (random (length choices))))
  40. (defun flatten (the-list)
  41. "Append together elements (or lists) in the list."
  42. (mappend #'(lambda (x) (if (listp x) (flatten x) (list x))) the-list))
  43. ;; Circular lists
  44. (defun make-circular (items)
  45. "Make items list circular"
  46. (setf (cdr (last items)) items))
  47. (defmacro push-circular (obj circ)
  48. "Move circ list and set head to obj"
  49. `(progn
  50. (pop ,circ)
  51. (setf (car ,circ) ,obj)))
  52. (defmacro peek-circular (circ)
  53. "Get head of circular list"
  54. `(car ,circ))
  55. (defmacro pop-circular (circ)
  56. "Get head of circular list"
  57. `(pop ,circ))
  58. (defun flat-circular (circ)
  59. "Flattens circular list"
  60. (do ((cur (cdr circ) (cdr cur))
  61. (head circ)
  62. result)
  63. ((eq head cur)
  64. (nreverse (push (car cur) result)))
  65. (push (car cur) result)))
  66. (defun http-default (url)
  67. (let ((uri (puri:uri url)))
  68. (puri:render-uri
  69. (if (null (puri:uri-scheme uri))
  70. (puri:uri (format nil "http://~A" url))
  71. uri)
  72. nil)))
  73. ;; XML processing
  74. (defun xml-request (url)
  75. (multiple-value-bind (raw-body status headers uri http-stream)
  76. (drakma:http-request (http-default url)
  77. :force-binary t
  78. :decode-content t)
  79. (declare (ignore status headers http-stream))
  80. (let* ((dom
  81. (handler-case
  82. (plump:parse (flex:octets-to-string raw-body :external-format :utf-8))
  83. (flex:external-format-encoding-error ()
  84. (plump:parse (flex:octets-to-string raw-body)))))
  85. (encoding (ignore-errors
  86. (plump:get-attribute (plump:first-child dom) "encoding"))))
  87. (values
  88. (if (and encoding (not (equal encoding "utf-8")))
  89. (plump:parse (flex:octets-to-string
  90. raw-body
  91. :external-format (intern encoding 'keyword)))
  92. dom)
  93. uri))))
  94. (defun get-by-tag (node tag)
  95. (nreverse (org.shirakumo.plump.dom::get-elements-by-tag-name node tag)))
  96. ;; JSON processing
  97. (defun json-request (url &key (method :get) parameters (object-as :alist))
  98. (multiple-value-bind (stream status headers uri http-stream)
  99. (drakma:http-request (http-default url) :method method :parameters parameters
  100. :external-format-out :utf-8
  101. :force-binary t :want-stream t :decode-content t)
  102. (declare (ignore status headers))
  103. (unwind-protect
  104. (progn
  105. (setf (flex:flexi-stream-external-format stream) :utf-8)
  106. (values (yason:parse stream :object-as object-as) uri))
  107. (ignore-errors (close http-stream)))))
  108. (defun format-ts (ts)
  109. (local-time:format-timestring nil ts
  110. :format '(:year "-" (:month 2) "-" (:day 2) " "
  111. (:hour 2) ":" (:min 2) ":" (:sec 2))))
  112. ;; Fix bug in local-time (following symlinks in /usr/share/zoneinfo/
  113. ;; leads to bad cutoff)
  114. (in-package #:local-time)
  115. (defun reread-timezone-repository (&key (timezone-repository *default-timezone-repository-path*))
  116. (check-type timezone-repository (or pathname string))
  117. (multiple-value-bind (valid? error)
  118. (ignore-errors
  119. (truename timezone-repository)
  120. t)
  121. (unless valid?
  122. (error "REREAD-TIMEZONE-REPOSITORY was called with invalid PROJECT-DIRECTORY (~A). The error is ~A."
  123. timezone-repository error)))
  124. (let* ((root-directory timezone-repository)
  125. (cutoff-position (length (princ-to-string root-directory))))
  126. (flet ((visitor (file)
  127. (handler-case
  128. (let* ((full-name (subseq (princ-to-string file) cutoff-position))
  129. (name (pathname-name file))
  130. (timezone (%realize-timezone (make-timezone :path file :name name))))
  131. (setf (gethash full-name *location-name->timezone*) timezone)
  132. (map nil (lambda (subzone)
  133. (push timezone (gethash (subzone-abbrev subzone)
  134. *abbreviated-subzone-name->timezone-list*)))
  135. (timezone-subzones timezone)))
  136. (invalid-timezone-file () nil))))
  137. (setf *location-name->timezone* (make-hash-table :test 'equal))
  138. (setf *abbreviated-subzone-name->timezone-list* (make-hash-table :test 'equal))
  139. (cl-fad:walk-directory root-directory #'visitor :directories nil :follow-symlinks nil
  140. :test (lambda (file)
  141. (not (find "Etc" (pathname-directory file) :test #'string=))))
  142. (cl-fad:walk-directory (merge-pathnames "Etc/" root-directory) #'visitor :directories nil))))
  143. (let ((zonepath "/usr/share/zoneinfo/"))
  144. (when (directory zonepath)
  145. (local-time:reread-timezone-repository :timezone-repository zonepath)))