f60d174591
Turns out this is an annoying thingy you sometimes to implement independently from formatting an entire timestamp, so we expose it for reuse. Change-Id: I11de2823eb03849ea78fc79e2f546e413882930f Reviewed-on: https://cl.tvl.fyi/c/depot/+/3163 Tested-by: BuildkiteCI Reviewed-by: grfn <grfn@gws.fyi> Reviewed-by: tazjin <mail@tazj.in>
117 lines
3.6 KiB
Common Lisp
117 lines
3.6 KiB
Common Lisp
(in-package #:klatre)
|
|
(declaim (optimize (safety 3)))
|
|
|
|
(defmacro comment (&rest _))
|
|
|
|
(defun posp (n) (> n 0))
|
|
|
|
;;; Sequence utilities
|
|
|
|
(defun slice (vector start end)
|
|
(make-array (- end start)
|
|
:element-type (array-element-type vector)
|
|
:displaced-to vector
|
|
:displaced-index-offset start))
|
|
|
|
(defun chunk-vector (size vector &key start end sharedp)
|
|
(check-type size (integer 1))
|
|
(loop
|
|
with slicer = (if sharedp #'slice #'subseq)
|
|
and low = (or start 0)
|
|
and high = (or end (length vector))
|
|
for s from low below high by size
|
|
for e from (+ low size) by size
|
|
collect (funcall slicer vector s (min e high))))
|
|
|
|
(defun chunk-list/unbounded (size list)
|
|
(loop
|
|
for front = list then next
|
|
for next = (nthcdr size front)
|
|
collect (ldiff front next)
|
|
while next))
|
|
|
|
(defun chunk-list/bounded (size list upper-limit)
|
|
(loop
|
|
for front = list then next
|
|
for next = (nthcdr (min size upper-limit) front)
|
|
collect (ldiff front next)
|
|
do (decf upper-limit size)
|
|
while (and next (plusp upper-limit))))
|
|
|
|
(defun chunk-list (size list &key (start 0) end)
|
|
"Returns successive chunks of list of size SIZE, starting at START and ending
|
|
at END."
|
|
(declare (inline check-list/bounded check-list/simple))
|
|
(check-type size (integer 1))
|
|
(let ((list (nthcdr start list)))
|
|
(when list
|
|
(if end
|
|
(chunk-list/bounded size list (- end start))
|
|
(chunk-list/unbounded size list)))))
|
|
|
|
(defun mapconcat (func lst sep)
|
|
"Apply FUNC to each element of LST, and concat the results as strings,
|
|
separated by SEP."
|
|
(check-type lst cons)
|
|
(check-type sep (simple-array character (*)))
|
|
(let ((vs (make-array 0
|
|
:element-type 'character
|
|
:fill-pointer 0
|
|
:adjustable t))
|
|
(lsep (length sep)))
|
|
(mapcar #'(lambda (str)
|
|
(let ((nstr (the (simple-array character (*))
|
|
(funcall func str))))
|
|
(dotimes (j (length nstr) j)
|
|
(vector-push-extend (char nstr (the fixnum j)) vs))
|
|
(dotimes (k lsep k)
|
|
(vector-push-extend (char sep (the fixnum k)) vs))))
|
|
lst)
|
|
vs))
|
|
|
|
;;;
|
|
;;; String handling
|
|
;;;
|
|
|
|
(defparameter dottime-format
|
|
'((:year 4) #\- (:month 2) #\- (:day 2)
|
|
#\T
|
|
(:hour 2) #\· (:min 2))
|
|
"`:LOCAL-TIME' format specifier for dottime")
|
|
|
|
(defun format-dottime (timestamp &optional (offset 0))
|
|
"Return TIMESTAMP formatted as dottime, with a specified offset or +00"
|
|
(check-type timestamp local-time:timestamp)
|
|
(concatenate 'string
|
|
(local-time:format-timestring nil timestamp
|
|
:format dottime-format
|
|
:timezone local-time:+utc-zone+)
|
|
(format-dottime-offset offset)))
|
|
|
|
(defun format-dottime-offset (offset)
|
|
"Render OFFSET in hours in the format specified by dottime."
|
|
(check-type offset integer)
|
|
(concatenate 'string
|
|
; render sign manually since format prints it after padding
|
|
(if (>= offset 0) "+" "-")
|
|
(format nil "~2,'0D" (abs offset))))
|
|
|
|
(comment
|
|
(format-dottime (local-time:now))
|
|
(format-dottime (local-time:now) 2))
|
|
|
|
(defun try-parse-integer (str)
|
|
"Attempt to parse STR as an integer, returning nil if it is invalid."
|
|
(check-type str string)
|
|
(handler-case (parse-integer str)
|
|
(sb-int:simple-parse-error (_) nil)))
|
|
|
|
;;;
|
|
;;; Function utilities
|
|
;;;
|
|
|
|
(defun partial (f &rest args)
|
|
"Return a function that calls F with ARGS prepended to any remaining
|
|
arguments"
|
|
(lambda (&rest more-args)
|
|
(apply f (append args more-args))))
|