diff options
-rw-r--r-- | emacsconf-publish.el | 15 | ||||
-rw-r--r-- | emacsconf-upcoming.el | 129 | ||||
-rw-r--r-- | emacsconf.el | 188 |
3 files changed, 324 insertions, 8 deletions
diff --git a/emacsconf-publish.el b/emacsconf-publish.el index 7a54f03..55b3458 100644 --- a/emacsconf-publish.el +++ b/emacsconf-publish.el @@ -165,7 +165,7 @@ (if (eq (plist-get talk :format) 'wiki) (concat s " \n") (concat "<li>" s "</li>"))) - (emacsconf-link-file-formats-as-list talk (or extensions emacsconf-published-extensions)) + (emacsconf-link-file-formats-as-list talk (or extensions emacsconf-main-extensions)) "") :poster (and video-file (format "https://media.emacsconf.org/%s/%s.png" (plist-get talk :conf-year) (file-name-base video-file))) :toobnix-info (if (plist-get talk :toobnix-url) @@ -384,7 +384,7 @@ ${info} (pcase emacsconf-publishing-phase ('program "<tr><th>Status</th><th>Title<th><th>Speaker(s)</th></tr>") ('schedule "<tr><th>Status</th><th>Start</th><th>Title</th><th>Speaker(s)</th></tr>") - ('resources "<tr><th>Title</th><th>Speaker(s)</th><th>Resources</th></tr>")) + ('resources "<tr><th>Title</th><th>Speaker(s)</th><th>Resources</th></tr>")) (mapconcat (lambda (o) (let* ((time-fmt "%l:%M %p") @@ -430,7 +430,7 @@ ${info} (emacsconf-link-file-formats-as-list (append o (list :base-url (format "%s%s/" emacsconf-media-base-url emacsconf-year))) - (append emacsconf-published-extensions '("--main.webm"))) + (append emacsconf-main-extensions '("--main.webm"))) ""))))))) (seq-remove (lambda (o) (string= (plist-get o :status) "CANCELLED")) (cdr info)) @@ -538,7 +538,7 @@ ${info} :track-base-url (format "/%s/captions/" (plist-get f :conf-year))) f) - emacsconf-published-extensions) + emacsconf-main-extensions) "") (if (plist-get f :qa-public) (emacsconf-index-card @@ -595,13 +595,12 @@ ${info} "") (mapconcat (lambda (lang) - (let ((lang-file (concat (file-name-sans-extension filename) "_" (car lang) (file-name-extension filename)))) + (let ((lang-file (concat (file-name-sans-extension filename) "_" (car lang) "." (file-name-extension filename)))) (if (file-exists-p lang-file) - (format "<track label=\"%s\" kind=\"captions\" srclang=\"%s\" src=\"%s--main_%s.vtt\" />" + (format "<track label=\"%s\" kind=\"captions\" srclang=\"%s\" src=\"%s\" />" (cdr lang) (car lang) - (concat (or track-base-url "") (file-name-nondirectory lang-file)) - (car lang)) + (concat (or track-base-url "") (file-name-nondirectory lang-file))) ""))) '(("fr" . "French") ("ja" . "Japanese")) ""))) diff --git a/emacsconf-upcoming.el b/emacsconf-upcoming.el new file mode 100644 index 0000000..b94dc00 --- /dev/null +++ b/emacsconf-upcoming.el @@ -0,0 +1,129 @@ +;;; emacsconf-upcoming.el --- Update upcoming.org with information about the next talks -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 Sacha Chua + +;; Author: Sacha Chua <sacha@sachachua.com> +;; Keywords: data + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; + +;;; Code: + +(defun emacsconf-upcoming-update-file () + (interactive) + (with-current-buffer (find-file-noselect emacsconf-upcoming-file) + (save-excursion + (org-map-entries #'emacsconf-upcoming-insert-or-update "SLUG={.}") +))) + +(defun emacsconf-upcoming-add-subtree () + (interactive) + (org-map-entries #'emacsconf-upcoming-insert-or-update "SLUG={.}" 'tree) + (save-excursion + (with-current-buffer (find-file-noselect emacsconf-upcoming-file) + (emacsconf-upcoming-sort)))) + +(defun emacsconf-upcoming-sort () + (interactive) + ;; Sort + (goto-char (point-min)) + (when (looking-at "\\*") (save-excursion (insert "\n"))) ;; Need to be before the first heading. + (org-sort-entries + nil ?f + (lambda () (cons (org-entry-is-done-p) (org-entry-get (point) "SCHEDULED"))) + (lambda (a b) + (cond + ((and (car a) (car b)) (string< (cdr a) (cdr b))) + ((car b) t) + ((car a) nil) ; move done things to the bottom + (t (string< (cdr a) (cdr b))))))) + +(defun emacsconf-upcoming-insert-or-update-from-slug (slug) + (interactive (list (emacsconf-complete-talk))) + (save-excursion + (emacsconf-with-talk-heading slug + (emacsconf-upcoming-insert-or-update nil nil)))) + +(defun emacsconf-upcoming-insert-or-update (&optional info sort) + (interactive (list nil nil)) + (let ((info + (or info + (when (buffer-file-name) + (if (string= (expand-file-name (buffer-file-name)) emacsconf-org-file) + (emacsconf-get-talk-info-for-subtree) + (let ((slug (org-entry-get (point) "SLUG"))) + (emacsconf-with-talk-heading slug + (emacsconf-get-talk-info-for-subtree))))))) + pos) + (when (and (plist-get info :slug) emacsconf-upcoming-file) + (with-current-buffer (find-file-noselect emacsconf-upcoming-file) + (setq pos (org-find-property "SLUG" (plist-get info :slug))) + (if pos + (goto-char pos) + (goto-char (point-max)) + (unless (bolp) (insert "\n")) + (insert (format "* TODO %s: %s - %s\n" + (plist-get info :slug) + (plist-get info :title) + (plist-get info :speakers)))) + (org-todo (plist-get info :status)) + (org-entry-put (point) "SLUG" (plist-get info :slug)) + (org-entry-put (point) "URL" (concat emacsconf-base-url emacsconf-year "/talks/" (plist-get info :slug))) + (org-set-property "SCHEDULED" (plist-get info :scheduled)) + (unless (org-entry-get (point) "INTRO_NOTE") + (org-entry-put (point) "INTRO_NOTE" (plist-get info :intro-note))) + (when (or (plist-get info :pronouns) (plist-get info :pronunciation)) + (unless (org-entry-get (point) "OTHER_INFO") + (org-entry-put (point) "OTHER_INFO" + (string-join (delq nil (list (plist-get info :pronouns) (plist-get info :pronunciation))) " ")))) + (org-entry-put (point) "PRESENT" (or (plist-get info :present) "?")) + (org-entry-put (point) "DURATION" + (or (plist-get info :video-duration) + (concat "~ " (plist-get info :duration)))) + (org-entry-put (point) "BUFFER" + (format "%s (ending ~ %s)" + (plist-get info :buffer) + (format-time-string + "%l:%M%p" + (seconds-to-time + (+ + (time-to-seconds + (org-timestamp-to-time (org-timestamp-split-range (org-timestamp-from-string (plist-get info :scheduled))))) + (* 60 (string-to-number (plist-get info :duration))) + (* 60 (string-to-number (plist-get info :buffer))))) + emacsconf-timezone))) + (org-entry-put (point) "Q_AND_A" + (or (plist-get info :bbb-room) + (plist-get info :q-and-a) + "?")) + ;(org-entry-put (point) "IRC" + ; (or (plist-get info :irc) "?")) + (org-entry-put (point) "CHECK_IN" + (or (plist-get info :check-in) "?")) + (org-entry-put (point) "EMAIL" (plist-get info :email)) + (if (plist-get info :contact) + (org-entry-put (point) "CONTACT" (plist-get info :check-in))) + (if sort (emacsconf-upcoming-sort)))))) + + + + + + +(provide 'emacsconf-upcoming) +;;; emacsconf-upcoming.el ends here diff --git a/emacsconf.el b/emacsconf.el index 8f2be80..357b271 100644 --- a/emacsconf.el +++ b/emacsconf.el @@ -75,5 +75,193 @@ "Return the newest file in PATH. Optionally filter by FILTER." (car (sort (seq-remove #'file-directory-p (directory-files path 'full filter t)) #'file-newer-than-file-p))) + +(defun emacsconf-get-slug-from-string (search) + (if (listp search) (setq search (car search))) + (if (and search (string-match "\\(.*?\\) - " search)) + (match-string 1 search) + search)) +(defun emacsconf-go-to-talk (&optional search) + (interactive (list (emacsconf-complete-talk))) + (when search + (setq search (emacsconf-get-slug-from-string search)) + (pop-to-buffer (find-file-noselect emacsconf-org-file)) + (let ((choices + (save-excursion + (delq nil + (org-map-entries + (lambda () + (when (org-entry-get (point) "SLUG") + (cons + (concat (org-entry-get (point) "SLUG") " - " + (org-entry-get (point) "ITEM") " - " + (org-entry-get (point) "NAME") " - " + (org-entry-get (point) "EMAIL")) + (point))))))))) + (goto-char + (if search + (or (org-find-property "SLUG" search) + (cdr (seq-find (lambda (s) (string-match search (car s))) choices))) + (assoc-default (completing-read "Find: " choices) + choices))) + (org-reveal)))) + +(defmacro emacsconf-with-talk-heading (search &rest body) + (declare (indent 1) (debug t)) + `(progn + (emacsconf-go-to-talk ,search) + ,@body)) + +(defun emacsconf-status-types () + ;; TODO + ) + +(defun emacsconf-get-talk-info-from-properties (o) + (let ((heading (org-heading-components)) + (field-props '((:title "ITEM") + (:talk-id "TALK_ID") + (:slug "SLUG") + (:video-slug "VIDEO_SLUG") + (:public "PUBLIC") + (:qa-public "QA_PUBLIC") + (:scheduled "SCHEDULED") + (:uuid "UUID") + (:email "EMAIL") + (:caption-note "CAPTION_NOTE") + (:duration "TIME") + (:q-and-a "Q_AND_A") + (:bbb-room "ROOM") + (:irc "IRC") + (:intro-note "INTRO_NOTE") + (:check-in "CHECK_IN") + (:contact "CONTACT") + (:captioner "CAPTIONER") + (:youtube-url "YOUTUBE_URL") + (:toobnix-url "TOOBNIX_URL") + (:pronunciation "PRONUNCIATION") + (:pronouns "PRONOUNS") + (:buffer "BUFFER") + (:time "MIN_TIME") + (:present "PRESENT") + (:speakers "NAME") + (:speakers-short "NAME_SHORT") + (:video-file "VIDEO_FILE") + (:video-file-size "VIDEO_FILE_SIZE") + (:video-duration "VIDEO_DURATION") + (:alternate-apac "ALTERNATE_APAC") + (:extra-live-time "EXTRA_LIVE_TIME")))) + (apply + 'append + o + (list + :type (if (org-entry-get (point) "SLUG") 'talk 'headline) + :status (elt heading 2) + :level (car heading) + :url (concat emacsconf-base-url emacsconf-year "/talks/" (org-entry-get (point) "SLUG")) + :schedule-group + (org-entry-get-with-inheritance "SCHEDULE_GROUP") + :wiki-file-path (expand-file-name + (concat (org-entry-get (point) "SLUG") ".md") + (expand-file-name "captions" (expand-file-name emacsconf-year emacsconf-directory))) + :conf-year emacsconf-year + :start-time (when (org-entry-get (point) "SCHEDULED") + (org-timestamp-to-time + (org-timestamp-split-range + (org-timestamp-from-string + (org-entry-get (point) "SCHEDULED"))))) + :end-time (when (org-entry-get (point) "SCHEDULED") + (org-timestamp-to-time + (org-timestamp-split-range + (org-timestamp-from-string + (org-entry-get (point) "SCHEDULED")) + t)))) + (mapcar + (lambda (o) (list (car o) (org-entry-get (point) (cadr o)))) + field-props)))) + +(defun emacsconf-get-abstract-from-wiki (o) + (plist-put o :markdown (emacsconf-talk-markdown-from-wiki (plist-get o :slug)))) + +(defun emacsconf-add-talk-status (o) + (plist-put o :status-label + (assoc-default (plist-get o :status) + (emacsconf-status-types) 'string= ""))) + +(defvar emacsconf-talk-info-functions '(emacsconf-get-talk-info-from-properties emacsconf-add-talk-status)) + +(defun emacsconf-get-talk-info-for-subtree () + (seq-reduce (lambda (prev val) (funcall val prev)) + emacsconf-talk-info-functions + nil)) + +(defun emacsconf-get-talk-info (&optional description-source) + (with-current-buffer (find-file-noselect emacsconf-org-file) + (save-excursion + (let (talk results (status-types (emacsconf-status-types))) + (org-map-entries + (lambda () + (when (or (org-entry-get (point) "SLUG") + (org-entry-get (point) "INCLUDE_IN_INFO")) + (setq results + (cons (emacsconf-get-talk-info-for-subtree) + results))))) + (nreverse results))))) + +(defun emacsconf-filter-talks (list) + "Return only talk info in LIST." + (seq-filter + (lambda (talk) (eq (plist-get talk :type) 'talk)) + list)) + +(defun emacsconf-get-talk-info-from-file (&optional filename) + (with-temp-buffer + (insert-file-contents (or filename "conf.org")) + (org-mode) + (org-show-all) + (goto-char (point-min)) + (goto-char (org-find-property "ID" "talks")) + (emacsconf-get-talk-info 'wiki))) + + +(defun emacsconf-find-talk-info (filter &optional info) + (setq info (or info (emacsconf-filter-talks (emacsconf-get-talk-info)))) + (when (stringp filter) (setq filter (list filter))) + (or (seq-find (lambda (o) (string= (plist-get o :slug) (car filter))) info) + (seq-find (lambda (o) + (let ((case-fold-search t) + (all (mapconcat (lambda (f) (plist-get o f)) '(:title :speakers :slug) " "))) + (null (seq-contains-p + (mapcar (lambda (condition) (string-match condition all)) filter) + nil)))) + info))) + +(defun emacsconf-goto-talk-id (id) + (goto-char (org-find-property "TALK_ID" id))) + +(defun emacsconf-talk-markdown-from-wiki (slug) + "Return the markdown from SLUG." + (when (file-exists-p (expand-file-name (format "%s/talks/%s.md" emacsconf-year slug) emacsconf-directory)) + (with-temp-buffer + (insert-file-contents (expand-file-name (format "%s/talks/%s.md" emacsconf-year slug) emacsconf-directory)) + (goto-char (point-min)) + (while (re-search-forward "<!--" nil t) + (let ((start (match-beginning 0))) + (when (re-search-forward "-->" nil t) + (delete-region start (match-end 0))))) + (goto-char (point-min)) + (while (re-search-forward "\\[\\[![^]]+\\]\\]" nil t) + (replace-match "")) + (string-trim (buffer-string))))) + +(defun emacsconf-replace-plist-in-string (attrs string) + "Replace ${keyword} from ATTRS in STRING." + (let ((a attrs)) + (while a + (setq string + (replace-regexp-in-string (regexp-quote (concat "${" (substring (symbol-name (pop a)) 1) "}")) + (pop a) + string t t))) + string)) + (provide 'emacsconf) ;;; emacsconf.el ends here |