util.lisp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. ;;;; Copyright © 2023, Jaidyn Ann <jadedctrl@posteo.at>
  2. ;;;;
  3. ;;;; This program is free software: you can redistribute it and/or
  4. ;;;; modify it under the terms of the GNU General Public License as
  5. ;;;; published by the Free Software Foundation, either version 3 of
  6. ;;;; the License, or (at your option) any later version.
  7. ;;;;
  8. ;;;; This program is distributed in the hope that it will be useful,
  9. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;;;; GNU General Public License for more details.
  12. ;;;;
  13. ;;;; You should have received a copy of the GNU General Public License
  14. ;;;; along with this program. If not, see <https://www.gnu.org/licenses/>.
  15. ;;;; FLORA-SEARCH-AURORA.UTIL
  16. ;;;; Useful misc. utilities used in multiple packages.
  17. ;;;; Let's get to it, we're on a deadline!
  18. (in-package :flora-search-aurora.util)
  19. ;;; ———————————————————————————————————
  20. ;;; Linewrapping & its helpers
  21. ;;; ———————————————————————————————————
  22. (defun search-all (subseq sequence &key (start 0))
  23. "Given a SUBSEQ to search for within a SEQUENCE, return every instance of
  24. SUBSEQ in SEQUENCE."
  25. (let ((matches '()))
  26. (loop while (setf start (search subseq sequence :start2 start))
  27. do (progn (pushnew start matches)
  28. (incf start)))
  29. (reverse matches))) ;; So they’re in ascending order!
  30. (defun closest-below (num number-list)
  31. "Given a NUMBER-LIST, return a descending list of member numbers below NUM."
  32. (sort
  33. (remove-if-not (lambda (a) (and (numberp a) (<= a num))) number-list)
  34. #'>))
  35. (defun fit-lines (string width &key (alignment :center))
  36. "Fit each line of a STING into a specific WIDTH, with ALIGNMENT to a specific
  37. side (either :CENTER, :LEFT, or :RIGHT)."
  38. (str:unlines
  39. (mapcar (lambda (line)
  40. (str:fit width line :pad-side alignment))
  41. (str:lines string))))
  42. (defun linewrap-string (string width)
  43. "Break a STRING into several lines, each one no larger than WIDTH. Uses
  44. newlines and hypens (to break long words) as necessary."
  45. (let* ((string (str:replace-all (string #\newline) " " string))
  46. (spaces (append '(0) (search-all " " string)))
  47. (index width))
  48. (loop while (< index (length string))
  49. do (let ((closest-space (car (closest-below index spaces)))
  50. (old-index (- index width)))
  51. (if (or (<= closest-space old-index)
  52. (> closest-space index))
  53. ;; Break up long words with a hyphen
  54. (return
  55. (linewrap-string
  56. (str:insert "- " (- index 1) string)
  57. width))
  58. ;; Replace eligible spaces with newlines uwu
  59. (progn
  60. (setf (elt string closest-space) #\newline)
  61. (setf index (+ closest-space width)))))
  62. finally (return string))))
  63. ;;; ———————————————————————————————————
  64. ;;; Listic affairs
  65. ;;; ———————————————————————————————————
  66. (defun every-other-element (list)
  67. "Collect every-other-element of a list. E.g., (1 2 3 4) → (1 3)."
  68. (when list
  69. (cons (car list)
  70. (every-other-element (cddr list)))))
  71. (defun plist= (a b &key (test #'eql))
  72. "Return whether or not two property lists are equal, by comparing values of each pair.
  73. Uses the keys of plist a."
  74. (let ((keys (every-other-element a)))
  75. (loop for key in keys
  76. do (when (not (apply test (list (getf a key)
  77. (getf b key))))
  78. (return nil))
  79. finally (return 't))))
  80. ;;; ———————————————————————————————————
  81. ;;; Numeric affairs
  82. ;;; ———————————————————————————————————
  83. (defmacro incf-0 (place &optional (Δ 1))
  84. "INCF the given PLACE, if it’s a number. If not a number, then set it to zero."
  85. `(if (numberp ,place)
  86. (incf ,place ,Δ)
  87. (setf ,place 0)))
  88. (defun at-least (minimum num)
  89. "This function returns at least every hope and dream you've ever had, and at
  90. maximum returns your more pitiful of moments."
  91. (if (< num minimum)
  92. minimum
  93. num))
  94. (defun at-most (maximum num)
  95. "This function returns at most every hope and dream you've ever had, and at
  96. minimum returns your more pitiful of moments."
  97. (if (> num maximum)
  98. maximum
  99. num))
  100. ;;; ———————————————————————————————————
  101. ;;; Linguistic & symbolic affirs
  102. ;;; ———————————————————————————————————
  103. (defmacro remove-from-alistf (key alist &key (test 'eql))
  104. "Remove the given item from an associative list destructively."
  105. `(alexandria:removef
  106. ,alist ,key
  107. :test (lambda (key item) (,test key (car item)))))
  108. (defun string->symbol (string)
  109. "Given a STRING with an optionally defined package (e.g., “package:symbol”),
  110. return it as an appopriate symbol."
  111. (let* ((split (str:split ":" (string-upcase string)))
  112. (package (when (eq (length split) 2)
  113. (car split)))
  114. (symbol (or (cadr split) (car split))))
  115. (if package
  116. (intern symbol package)
  117. (intern symbol))))
  118. (defun langcode->keysym (str)
  119. "Given a language’s code (es/cz/it/etc.), return a corresponding key symbol,
  120. if the language is among the supported. Otherwise, nil."
  121. (when (stringp str)
  122. (let ((lang (string-downcase (subseq str 0 2))))
  123. (cond
  124. ((string-equal lang "eo") :eo)
  125. ((string-equal lang "en") :en)))))
  126. (defun system-language ()
  127. "Return the system language, if among the supported; otherwise, EN-glish."
  128. (or (langcode->keysym (uiop:getenv "LANG"))
  129. :en))
  130. (defun getf-lang (plist &key language (fallback-lang :en))
  131. "With a PLIST containing keys of language-codes, return the property either fitting the
  132. preferred LANGUAGE, or the backup FALLBACK-LANG (if LANGUAGE’s is NIL)."
  133. (or (getf plist (or language (ignore-errors *language*) (system-language)))
  134. (getf plist fallback-lang)))
  135. (defparameter *language* (…:system-language))