src/main.lisp @ 13d4b6ab6f9c
default tip
Clean up a bit
author |
Steve Losh <steve@stevelosh.com> |
date |
Thu, 12 Jun 2025 16:09:29 -0400 |
parents |
da9138afa986 |
children |
(none) |
(in-package :planner)
;;;; Config -------------------------------------------------------------------
(defvar *smtp-api-token* nil)
(defvar *smtp-user-name* nil)
(defvar *zip-code* nil)
(defparameter *from-address* "plan@stevelosh.com")
(defparameter *display-name* "Plan")
(defparameter *to-address* "steve@stevelosh.com")
(defparameter *smtp-server* "smtp.fastmail.com")
(defun safely-read-1 (stream packages)
(handler-case
(loop :for value = (safe-read:safe-read stream packages)
:when value :do (return (values value t)))
(end-of-file ()
(values nil nil))))
(defun load-config ()
(let* ((all-config-paths (uiop:xdg-config-pathnames "planner/config.sexp"))
(config-paths (reverse (remove-if-not #'probe-file all-config-paths))))
(dolist (config-path config-paths)
(with-open-file (f config-path)
(let ((config (safely-read-1 f (list :planner/conf))))
(setf
*zip-code* (getf config 'planner/conf:zip-code *zip-code*)
*smtp-user-name* (getf config 'planner/conf:smtp-user-name *smtp-user-name*)
*smtp-api-token* (getf config 'planner/conf:smtp-api-token *smtp-api-token*)))))
(assert (not (null *zip-code*)) () "zip-code not configured in any of ~{~A~^ ~}" all-config-paths)
(assert (not (null *smtp-user-name*)) () "smtp-user-name not configured in any of ~{~A~^ ~}" all-config-paths)
(assert (not (null *smtp-api-token*)) () "smtp-api-token not configured in any of ~{~A~^ ~}" all-config-paths)))
;;;; Date ---------------------------------------------------------------------
(defun date-iso ()
(local-time:format-timestring
nil (local-time:now)
:format '((:year 4) #\- (:month 2) #\- (:day 2))))
(defun date-human ()
(local-time:format-timestring
nil (local-time:now)
:format '(:long-weekday ", " :long-month " " :day ", " :year)))
;;;; Weather ------------------------------------------------------------------
(defun weather ()
(sh `("weather" "--hours" "20" ,*zip-code*) :result-type 'string))
;;;; Main ---------------------------------------------------------------------
(defun build-subject ()
(format nil "[plan/~A] Plan for ~A" (date-iso) (date-human)))
(defun header (string &key (width 60))
(if (>= (length string) (1- width))
string
(format nil "~A ~v,,,'=@A" string (- width (length string) 1) "")))
(defun build-content ()
(format nil
"Plan for ~A.~2%~
~
~A~2%~A"
(date-human)
(header "Weather") (weather)))
(defun build-email ()
(values (build-subject) (build-content)))
(defun print-email (subject content)
(format t "From: ~A~%~
To: ~A~%~
Subject: ~A~2%~
~
~A~%"
*from-address*
*to-address*
subject
content))
(defun send-email (subject content)
(cl-smtp:send-email
*smtp-server*
*from-address*
*to-address*
subject
content
:ssl :tls
:port 465
:display-name "Plan"
:authentication (list *smtp-user-name* *smtp-api-token*)
:html-message (format nil "<pre>~A</pre>" content)))
(defun run (&key (dry-run t))
(load-config)
(multiple-value-bind (subject content) (build-email)
(if dry-run
(print-email subject content)
(send-email subject content))
(values)))
;;;; UI -----------------------------------------------------------------------
(adopt:define-string *documentation*
"Planner sends planning emails.")
(defparameter *o/help*
(adopt:make-option 'help
:help "display help and exit"
:long "help"
:short #\h
:reduce (constantly t)))
(defparameter *o/dry-run*
(adopt:make-option 'dry-run
:help "print email to stdout instead of sending it"
:long "dry-run"
:short #\n
:reduce (constantly t)))
(defparameter *ui*
(adopt:make-interface
:name "planner"
:usage "[OPTIONS]"
:summary "A Common Lisp bot for sending emails to help me plan."
:help *documentation*
:contents
(list *o/help*
*o/dry-run*)))
(defun toplevel ()
(adopt::quit-on-ctrl-c ()
(multiple-value-bind (arguments options) (adopt:parse-options *ui*)
(when (gethash 'help options)
(adopt:print-help-and-exit *ui*))
(unless (null arguments)
(adopt:print-error-and-exit "No arguments supported."))
(run :dry-run (gethash 'dry-run options)))))