pgloader/src/main.lisp

178 lines
6.0 KiB
Common Lisp

(in-package #:pgloader)
(defun list-encodings ()
"List known encodings names and aliases from charsets::*lisp-encodings*."
(format *standard-output* "Name ~30TAliases~%")
(format *standard-output* "--------~30T--------------~%")
(loop
with encodings = (sort (copy-tree charsets::*lisp-encodings*) #'string<
:key #'car)
for (name . aliases) in encodings
do (format *standard-output* "~a~30T~{~a~^, ~}~%" name aliases))
(terpri))
(defun log-threshold (min-message &key quiet verbose debug)
"Return the internal value to use given the script parameters."
(cond (debug :debug)
(verbose :info)
(quiet :warning)
(t (or (find-symbol (string-upcase min-message) "KEYWORD")
:notice))))
(defparameter *opt-spec*
`((("help" #\h) :type boolean :documentation "Show usage and exit.")
(("version" #\V) :type boolean
:documentation "Displays pgloader version and exit.")
(("quiet" #\q) :type boolean :documentation "Be quiet")
(("verbose" #\v) :type boolean :documentation "Be verbose")
(("debug" #\d) :type boolean :documentation "Display debug level information.")
("client-min-messages" :type string :initial-value "warning"
:documentation "Filter logs seen at the console")
("log-min-messages" :type string :initial-value "notice"
:documentation "Filter logs seen in the logfile")
(("root-dir" #\D) :type string :initial-value ,*root-dir*
:documentation "Output root directory.")
(("upgrade-config" #\U) :type boolean
:documentation "Output the command(s) corresponding to .conf file for v2.x")
(("list-encodings" #\E) :type boolean
:documentation "List pgloader known encodings and exit.")
(("logfile" #\L) :type string
:documentation "Filename where to send the logs.")
(("load" #\l) :type string :list t :optional t
:documentation "Read user code from file")))
(defun print-backtrace (condition debug stream)
"Depending on DEBUG, print out the full backtrace or just a shorter
message on STREAM for given CONDITION."
(if debug
(trivial-backtrace:print-backtrace condition :output stream :verbose t)
(trivial-backtrace:print-condition condition stream)))
(defun mkdir-or-die (path debug &optional (stream *standard-output*))
"Create a directory at given PATH and exit with an error message when
that's not possible."
(handler-case
(ensure-directories-exist path)
(condition (e)
;; any error here is a panic
(if debug
(print-backtrace e debug stream)
(format stream "PANIC: ~a.~%" e))
(uiop:quit))))
(defun log-file-name (logfile)
" If the logfile has not been given by the user, default to using
pgloader.log within *root-dir*."
(cond ((null logfile)
(make-pathname :directory (directory-namestring *root-dir*)
:name "pgloader"
:type "log"))
((fad:pathname-relative-p logfile)
(merge-pathnames logfile *root-dir*))
(t
logfile)))
(defun main (argv)
"Entry point when building an executable image with buildapp"
(let ((args (rest argv)))
(multiple-value-bind (options arguments)
(command-line-arguments:process-command-line-options *opt-spec* args)
(destructuring-bind (&key help version quiet verbose debug logfile
list-encodings upgrade-config load
client-min-messages log-min-messages
root-dir)
options
;; First care about the root directory where pgloader is supposed to
;; output its data logs and reject files
(setf *root-dir* (fad:pathname-as-directory root-dir))
(mkdir-or-die *root-dir* debug)
;; Set parameters that come from the environement
(init-params-from-environment)
;; Then process options
(when debug
#+sbcl
(format t "sb-impl::*default-external-format* ~s~%"
sb-impl::*default-external-format*)
(format t "tmpdir: ~s~%" *default-tmpdir*))
(when version
(format t "pgloader version ~s~%" *version-string*))
(when help
(format t "~a [ option ... ] command-file ..." (first argv))
(command-line-arguments:show-option-help *opt-spec*))
(when (or help version) (uiop:quit))
(when list-encodings
(list-encodings)
(uiop:quit))
(when upgrade-config
(loop for filename in arguments
do
(pgloader.ini:convert-ini-into-commands filename)
(format t "~%~%"))
(uiop:quit))
(when load
(loop for filename in load
do (load (compile-file filename :verbose nil :print nil))))
;; Now process the arguments
(when arguments
;; Start the logs system
(let ((*logfile* (log-file-name logfile))
(*log-min-messages*
(log-threshold log-min-messages
:quiet quiet :verbose verbose :debug debug))
(*client-min-messages*
(log-threshold client-min-messages
:quiet quiet :verbose verbose :debug debug)))
(with-monitor ()
;; tell the user where to look for interesting things
(log-message :log "Main logs in '~a'" *logfile*)
(log-message :log "Data errors in '~a'~%" *root-dir*)
;; process the files
(loop for filename in arguments
do
;; The handler-case is to catch unhandled exceptions at the
;; top level and continue with the next file in the list.
;;
;; The handler-bind is to be able to offer a meaningful
;; backtrace to the user in case of unexpected conditions
;; being signaled.
(handler-case
(handler-bind
((condition
#'(lambda (condition)
(log-message :fatal "We have a situation here.")
(print-backtrace condition debug *standard-output*))))
(run-commands (fad:canonical-pathname filename)
:start-logger nil)
(format t "~&"))
(condition (c)
(when debug (invoke-debugger c))
(uiop:quit 1)))))))
(uiop:quit)))))