;;; ;;; Random utilities ;;; (in-package :pgloader.utils) ;;; ;;; Camel Case converter ;;; (defun camelCase-to-colname (string) "Transform input STRING into a suitable column name. lahmanID lahman_id playerID player_id birthYear birth_year" (coerce (loop for first = t then nil for char across string for previous-upper-p = nil then char-upper-p for char-upper-p = (eq char (char-upcase char)) for new-word = (and (not first) char-upper-p (not previous-upper-p)) when (and new-word (not (char= char #\_))) collect #\_ collect (char-downcase char)) 'string)) ;;; ;;; Unquote SQLite default values, might be useful elsewhere ;;; (defun unquote (string &optional (quote #\') (escape #\\)) "Given '0', returns 0." (declare (type (or null simple-string) string)) (when string (let ((l (length string))) (cond ((and (<= 2 l) ; "string" (char= quote (aref string 0) (aref string (1- l)))) (subseq string 1 (1- l))) ((and (<= 4 l) ; \"string\" (char= escape (aref string 0) (aref string (- l 2))) (char= quote (aref string 1) (aref string (- l 1)))) (subseq string 2 (- l 2))) (t string))))) ;;; ;;; Process ~/ references at run-time (not at compile time!) ;;; (defun expand-user-homedir-pathname (namestring) "Expand NAMESTRING replacing leading ~ with (user-homedir-pathname)" (typecase namestring (pathname namestring) (string (cond ((or (string= "~" namestring) (string= "~/" namestring)) (user-homedir-pathname)) ((and (<= 2 (length namestring)) (char= #\~ (aref namestring 0)) (char= #\/ (aref namestring 1))) (uiop:merge-pathnames* (uiop:parse-unix-namestring (subseq namestring 2)) (user-homedir-pathname))) (t (uiop:parse-unix-namestring namestring)))))) ;;; ;;; For log messages ;;; (defun pretty-print-bytes (bytes &key (unit "B")) "Return a string to reprensent bytes in human readable format, with units" (let ((bytes (or bytes 0))) (loop :for multiple :in '("T" "G" "M" "k") :for power :in '(40 30 20 10 1) :for limit := (expt 2 power) :until (<= limit bytes) :finally (return (format nil "~5,1f ~a~a" (/ bytes limit) multiple unit))))) ;;; ;;; Defining ranges and partitions. ;;; (defun split-range (min max &optional (count *rows-per-range*)) "Split the range from MIN to MAX into sub-ranges of COUNT elements." (loop :for i := min :then j :for j := (+ i count) :while (< i max) :collect (list i (min j max)))) (defun distribute (list-of-ranges count) "Split a list of ranges into COUNT sublists." (let ((result (make-array count :element-type 'list :initial-element nil))) (loop :for i :from 0 :for range :in list-of-ranges :do (push range (aref result (mod i count)))) (map 'list #'reverse result )))