From b684768dc24263b1636a18c9c88d52884c317821 Mon Sep 17 00:00:00 2001 From: Sacha Chua Date: Sun, 17 Sep 2023 09:40:09 -0400 Subject: parse better, mail speakers --- emacsconf-mail.el | 83 ++++++++++++++++++++++++++++++++------------------- emacsconf-schedule.el | 65 ++++++++++++++++++++++++++++------------ emacsconf.el | 24 ++++++++++----- 3 files changed, 114 insertions(+), 58 deletions(-) diff --git a/emacsconf-mail.el b/emacsconf-mail.el index b0f5aa6..ff65868 100644 --- a/emacsconf-mail.el +++ b/emacsconf-mail.el @@ -156,6 +156,11 @@ Group by e-mail." (defun emacsconf-mail-group-by-email (info) (seq-group-by (lambda (o) (plist-get o :email)) info)) +(defun emacsconf-mail-speaker-from-slug (talk) + "E-mail the speaker for TALK." + (interactive (list (emacsconf-complete-talk-info))) + (compose-mail (plist-get talk :email))) + (defun emacsconf-mail-speaker (&optional subject body talk) "Compose a message to the speaker of the current talk." (interactive (list nil nil (emacsconf-complete-talk-info))) @@ -247,39 +252,54 @@ Group by e-mail." (defun emacsconf-mail-parse-submission (body) "Extract data from EmacsConf 2023 submissions in BODY." (when (listp body) (setq body (plist-get (car body) :content))) - (let ((data (list :body body)) - (fields '((:title "^[* ]*Talk title") - (:description "^[* ]*Talk description") - (:format "^[* ]*Format") - (:intro "^[* ]*Introduction for you and your talk") - (:name "^[* ]*Speaker name") - (:availability "^[* ]*Speaker availability") - (:q-and-a "^[* ]*Preferred Q&A approach") - (:public "^[* ]*Public contact information") - (:private "^[* ]*Private emergency contact information") - (:release "^[* ]*Please include this speaker release")))) + (let* ((data (list :body body)) + (fields '((:title "^[* ]*Talk title") + (:description "^[* ]*Talk description") + (:format "^[* ]*Format") + (:intro "^[* ]*Introduction for you and your talk") + (:name "^[* ]*Speaker name") + (:availability "^[* ]*Speaker availability") + (:q-and-a "^[* ]*Preferred Q&A approach") + (:public "^[* ]*Public contact information") + (:private "^[* ]*Private emergency contact information") + (:release "^[* ]*Please include this speaker release"))) + field + (field-regexp (mapconcat + (lambda (o) + (concat "\\(?:" (cadr o) "\\)")) + fields "\\|"))) (with-temp-buffer (insert body) (goto-char (point-min)) ;; Try to parse it - (while fields - ;; skip the field title - (when (and (or (looking-at (cadar fields)) - (re-search-forward (cadar fields) nil t)) - (re-search-forward "\\(:[ \t\n]+\\|\n\n\\)" nil t)) - ;; get the text between this and the next field - (setq data (plist-put data (caar fields) - (buffer-substring (point) - (or - (when (and (cdr fields) - (re-search-forward (cadr (cadr fields)) nil t)) - (goto-char (match-beginning 0)) - (point)) - (point-max)))))) - (setq fields (cdr fields))) + (catch 'done + (while (not (eobp)) + ;; skip the field title + (unless (looking-at field-regexp) + (unless (re-search-forward field-regexp nil t) + (throw 'done nil))) + (goto-char (match-beginning 0)) + (setq field (seq-find (lambda (o) + (looking-at (cadr o))) + fields)) + (when field + ;; get the text between this and the next field + (re-search-forward "\\(:[ \t\n]+\\|\n\n\\)" nil t) + (setq data + (plist-put + data + (car field) + (buffer-substring + (point) + (or (and + (re-search-forward field-regexp nil t) + (goto-char (match-beginning 0)) + (point)) + (point-max)))))))) (if (string-match "[0-9]+" (or (plist-get data :format) "")) (plist-put data :time (match-string 0 (or (plist-get data :format) "")))) - data))) + data) +)) ;;;###autoload (defun emacsconf-mail-review () @@ -293,9 +313,9 @@ Group by e-mail." (message-goto-body) (save-excursion (insert (format - "Thanks for submitting your proposal! (TODO: feedback) We're experimenting -with early acceptance this year, so we'll wait a week (~ %s) in case the -other volunteers want to chime in regarding your talk. =) + "Thanks for submitting your proposal! (TODO: feedback) + +We'll wait a week (~ %s) in case the other volunteers want to chime in regarding your talk. =) " notification-date))))) @@ -470,7 +490,8 @@ Include some other things, too, such as emacsconf-year, title, name, email, url, (format "from:%s or to:%s" o o)) (split-string (plist-get talk :email) " *, *") " or ") - " or (" emacsconf-id " and " (plist-get talk :slug) ")"))) + ;; " or (" emacsconf-id " and " (plist-get talk :slug) ")" + ))) ;;; Volunteers diff --git a/emacsconf-schedule.el b/emacsconf-schedule.el index 0cc261f..30cb110 100644 --- a/emacsconf-schedule.el +++ b/emacsconf-schedule.el @@ -365,11 +365,14 @@ Pairs with `emacsconf-schedule-dump-sexp'." (when track (dom-set-attribute node 'fill (plist-get track :color))))) -(defun emacsconf-schedule-svg-color-by-availability (o node &optional parent) - (dom-set-attribute node 'fill - (if (string-match "^[><]" (plist-get o :availability)) - "gray" - "green"))) +(defun emacsconf-schedule-svg-color-by-availability (o node &optional _) + (dom-set-attribute node 'fill + (cond + ((string-match "^<" (or (plist-get o :availability) "")) + "lightblue") + ((string-match "^>" (or (plist-get o :availability) "")) + "peachpuff") + (t "gray")))) (defun emacsconf-schedule-svg (width height &optional info) "Make the schedule SVG for INFO." @@ -406,12 +409,14 @@ Other status: gray" "PROCESSING" "TO_AUTOCAP")) "palegoldenrod") - ((rx (or "TO_ASSIGN")) + ("TO_ASSIGN" "yellow") - ((rx (or "TO_CAPTION")) + ("TO_CAPTION" "lightgreen") - ((rx (or "TO_STREAM")) + ("TO_STREAM" "green") + ("TODO" + "lightgray") (_ "gray"))))) (defun emacsconf-schedule-svg-days (width height days) @@ -570,7 +575,27 @@ Talks with a FIXED_TIME property are not moved." (setq end-time (time-add (org-get-scheduled-time (point)) (seconds-to-time duration))) (setq current-time (time-add end-time (* (string-to-number (or (plist-get talk :buffer) "0")) 60)))))))))))) -(defun emacsconf-schedule-validate-time-constraints (&optional info) +(defun emacsconf-schedule-get-key-func () + "Get the sorting key for the current entry." + (org-entry-get (point) "SLUG")) +(defun emacsconf-schedule-sort-compare-func (a b) + (let* ((entry-a (emacsconf-resolve-talk a emacsconf-schedule-draft)) + (entry-b (emacsconf-resolve-talk b emacsconf-schedule-draft)) + (track-index-a (or (seq-position emacsconf-tracks (emacsconf-get-track (plist-get entry-a :track))) 0)) + (track-index-b (or (seq-position emacsconf-tracks (emacsconf-get-track (plist-get entry-b :track))) 0))) + (cond + ((string= (plist-get entry-a :status) "CANCELLED") nil) + ((string= (plist-get entry-b :status) "CANCELLED") t) + ((< track-index-a track-index-b) t) + ((> track-index-a track-index-b) nil) + ((string< (plist-get entry-a :scheduled) + (plist-get entry-b :scheduled)) t) + (t nil)))) +(defun emacsconf-schedule-sort-entries () + (interactive) + (org-sort-entries nil ?f #'emacsconf-schedule-get-key-func #'emacsconf-schedule-sort-compare-func)) + +(defun emacsconf-schedule-validate-time-constraints (info &rest _) (interactive) (let* ((info (or info (emacsconf-get-talk-info))) (results (delq nil @@ -588,9 +613,9 @@ Talks with a FIXED_TIME property are not moved." (constraint (emacsconf-schedule-get-time-constraint o))) (when constraint (setq result (apply #'emacsconf-schedule-check-time - (plist-get o :slug) - o - constraint)) + (plist-get o :slug) + o + constraint)) (when result (plist-put o :invalid result)) result))) info))))) @@ -640,7 +665,7 @@ Both start and end time are tested." (when diff (list (concat "Missing talks: " (string-join diff ", ")))))) -(defun emacsconf-schedule-validate-no-duplicates (sched) +(defun emacsconf-schedule-validate-no-duplicates (sched &optional info) (let* ((sched-slugs (mapcar (lambda (o) (plist-get o :slug)) (emacsconf-filter-talks sched))) (dupes (seq-filter (lambda (o) (> (length (cdr o)) 1)) @@ -648,12 +673,14 @@ Both start and end time are tested." (when dupes (list (concat "Duplicate talks: " (mapconcat 'car dupes ", ")))))) +(defvar emacsconf-schedule-validation-functions '(emacsconf-schedule-validate-time-constraints + emacsconf-schedule-validate-live-q-and-a-sessions-are-staggered + emacsconf-schedule-validate-all-talks-present + emacsconf-schedule-validate-no-duplicates)) (defun emacsconf-schedule-validate (sched &optional info) - (append - (emacsconf-schedule-validate-time-constraints sched) - (emacsconf-schedule-validate-live-q-and-a-sessions-are-staggered sched) - (emacsconf-schedule-validate-all-talks-present sched info) - (emacsconf-schedule-validate-no-duplicates sched))) + (seq-mapcat (lambda (func) + (funcall func sched info)) + emacsconf-schedule-validation-functions)) (defun emacsconf-schedule-inflate-tracks (tracks schedule) (mapcar @@ -741,7 +768,7 @@ If emacsconf-schedule-apply is non-nil, update `emacsconf-org-file' and the wiki (defvar emacsconf-schedule-validate-live-q-and-a-sessions-buffer 5 "Number of minutes' allowance for a streamer to adjust audio and get set up. Try to avoid overlapping the start of live Q&A sessions.") -(defun emacsconf-schedule-validate-live-q-and-a-sessions-are-staggered (schedule) +(defun emacsconf-schedule-validate-live-q-and-a-sessions-are-staggered (schedule &rest _) "Try to avoid overlapping the start of live Q&A sessions. Return nil if there are no errors." (when emacsconf-schedule-validate-live-q-and-a-sessions-buffer diff --git a/emacsconf.el b/emacsconf.el index b73e816..f506626 100644 --- a/emacsconf.el +++ b/emacsconf.el @@ -280,14 +280,15 @@ (emacsconf-get-slug-from-string (emacsconf-complete-talk))) (defun emacsconf-export-slug (link description format _) - (let ((path (format "https://emacsconf.org/%s/talks/%s" emacsconf-year link)) - (desc (or description link))) + (let* ((path (format "https://emacsconf.org/%s/talks/%s" emacsconf-year link)) + (talk (emacsconf-resolve-talk link)) + (desc (or description link))) (pcase format (`html - (format "%s" link desc)) + (format "%s" link (plist-get talk :title) desc)) (`ascii (format "%s (%s)" desc path)) - (`markdown - (format "[[%s|%s/talks/%s]]" desc emacsconf-year link)) + (`md + (format "[%s](%s \"%s\")" desc path (plist-get talk :title))) (_ path)))) (with-eval-after-load 'org @@ -812,9 +813,9 @@ If INFO is specified, limit it to that list." prev))) ;; (emacsconf-previous-talk (emacsconf-resolve-talk "lspbridge")) -(defun emacsconf-resolve-talk (talk) +(defun emacsconf-resolve-talk (talk &optional info) "Return the plist for TALK." - (if (stringp talk) (emacsconf-find-talk-info talk) talk)) + (if (stringp talk) (emacsconf-find-talk-info talk info) talk)) (defun emacsconf-find-talk-info (filter &optional info) (setq info (or info (emacsconf-filter-talks (emacsconf-get-talk-info)))) @@ -921,7 +922,7 @@ If INFO is specified, limit it to that list." "u" #'emacsconf-update-talk "t" #'emacsconf-insert-talk-title "m" #'emacsconf-mail-speaker-from-slug - "n" #'emacsconf-notmuch-search-mail-from-entry + "n" #'emacsconf-mail-notmuch-search-for-talk "f" #'org-forward-heading-same-level "b" #'org-backward-heading-same-level "RET" #'emacsconf-go-to-talk) @@ -1556,5 +1557,12 @@ tracks with the ID in the cdr of that list." :complete #'emacsconf-ansible-complete :export #'emacsconf-ansible-export :follow #'emacsconf-ansible-open)) + +(defun emacsconf-end-of-week (date) + "Useful for analyzing data. Assumes week ends Sunday." + (let ((d (decode-time (date-to-time date)))) + (format-time-string "%Y-%m-%d" + (encode-time + (decoded-time-add d (make-decoded-time :day (% (- 7 (decoded-time-weekday d)) 7))))))) (provide 'emacsconf) ;;; emacsconf.el ends here -- cgit v1.2.3