X-Git-Url: https://git.korewanetadesu.com/?p=pelican-mode.git;a=blobdiff_plain;f=pelican-mode.el;h=3e710be74905bb828b51c33f8d4ff31664842315;hp=7f7460baf1e4576c6dedf24ede776e1666626ae6;hb=219b9af327862e3111f27457eb98d6c6e512899a;hpb=4c47190c4c9bdd1ae77d04e26e48f580ee6975bc diff --git a/pelican-mode.el b/pelican-mode.el index 7f7460b..3e710be 100644 --- a/pelican-mode.el +++ b/pelican-mode.el @@ -20,11 +20,32 @@ ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . + ;;; Commentary: ;; -;; Probably, this doesn't handle a lot of error cases. I also never -;; tested it on networked drives and the lookup for pelicanconf.py -;; might slow it down considerably. +;; pelican-mode is an Emacs minor mode for editing pages and posts in +;; Pelican sites. Pelican is a static site generator which can +;; process a variety of text file formats. For more information, see +;; URL https://blog.getpelican.com/. +;; +;; It's intended to be used alongside `markdown-mode' or `rst-mode'. +;; It also assumes you've set up Pelican with ``pelican-quickstart'' +;; or something like it. In particular it assumes: +;; +;; * The existence of ``pelicanconf.py'' and ``Makefile'' in some +;; ancestor directory. +;; * The first component of the path (e.g. ``content'') after that +;; ancestor is irrelevant. +;; * If the next component is ``pages'', that indicates a page +;; rather than an article. +;; +;; To enable by default on all text files in a Pelican site: +;; +;; (require 'pelican-mode) +;; (pelican-global-mode) +;; +;; Or, register `pelican-mode' or `pelican-mode-enable-if-site' +;; as hook functions for more direct control. ;;; Code: @@ -33,7 +54,9 @@ (require 'subr-x) (defgroup pelican-mode nil - "Support for Pelican articles and pages." + "Support for Pelican articles and pages. + +For more information about Pelican see URL https://blog.getpelican.com/." :group 'convenience) (defcustom pelican-mode-default-page-fields @@ -42,7 +65,7 @@ See the documentation for `pelican-mode-set-field' for more information about metadata fields and special values." - :group 'pelican + :group 'pelican-mode :type '(plist)) (defcustom pelican-mode-default-article-fields @@ -51,24 +74,26 @@ about metadata fields and special values." See the documentation for `pelican-mode-set-field' for more information about metadata fields and special values." - :group 'pelican + :group 'pelican-mode :type '(plist)) -(defcustom pelican-mode-set-field-alist +(defcustom pelican-mode-formats '((markdown-mode . pelican-mode-set-field-markdown-mode) + (adoc-mode . pelican-mode-set-field-adoc-mode) + (org-mode . pelican-mode-set-field-org-mode) (rst-mode . pelican-mode-set-field-rst-mode)) "Functions to handle setting metadata, based on major mode. This association list maps modes to functions that take two arguments, field and value strings." - :group 'pelican + :group 'pelican-mode :type '(alist :key-type function :value-type function)) (defun pelican-mode-timestamp (&optional time) "Generate a pelican-mode-compatible timestamp for TIME." (format-time-string "%Y-%m-%d %H:%M" time)) -(defun pelican-mode-insert-header (&rest fields) +(defun pelican-mode-set-fields (&rest fields) "Insert a Pelican header for an article with metadata FIELDS." (mapc (apply-partially #'apply #'pelican-mode-set-field) (seq-partition fields 2))) @@ -76,19 +101,23 @@ arguments, field and value strings." (defun pelican-mode-insert-draft-article-header (title tags) "Insert a Pelican header for a draft with a TITLE and TAGS." (interactive "sArticle title: \nsTags: ") - (apply #'pelican-mode-insert-header - `(:title ,title ,@pelican-mode-default-article-fields :tags ,tags))) + (apply #'pelican-mode-set-fields + `(:title ,title + ,@pelican-mode-default-article-fields + :tags ,tags))) (defun pelican-mode-insert-page-header (title &optional hidden) - "Insert a Pelican header for a page with a TITLE, potentially HIDDEN." - (interactive - (list (read-string "Page title: ") - (y-or-n-p "Hidden? "))) - (apply #'pelican-mode-insert-header - `(:title ,title ,@pelican-mode-default-page-fields - :hidden ,(when hidden "hidden")))) - -(defun pelican-mode-insert-auto-header () + "Insert a Pelican header for a page with a TITLE. + +If HIDDEN is non-nil, the page is marked hidden; otherwise it +has no status." + (interactive "sPage title: \nP") + (apply #'pelican-mode-set-fields + (append + (list :title title :status (when hidden "hidden")) + pelican-mode-default-page-fields))) + +(defun pelican-mode-insert-header () "Insert a Pelican header for a page or article." (interactive) (call-interactively @@ -106,8 +135,8 @@ arguments, field and value strings." (replace-match header) (insert header))) (let ((text (when value (format ":%s: %s\n" field value)))) - (when (re-search-forward "^#" nil t) - (forward-line 2)) + (when (looking-at "^.*\n#") + (forward-line 3)) (if (re-search-forward (format "^:%s:.*\n" (regexp-quote field)) nil t) (replace-match (or text "")) (when text @@ -126,21 +155,61 @@ arguments, field and value strings." (replace-match text) (insert text)))))) +(defun pelican-mode-set-field-adoc-mode (field value) + "Set AsciiDoc metadata FIELD to VALUE." + (setq field (downcase field)) + (if (equal field "title") + (let ((header (format "= %s\n\n" value))) + (if (looking-at "= .*\n\n+") + (replace-match header) + (insert header))) + (let ((text (when value (format ":%s: %s\n" field value)))) + (when (looking-at "^=") + (forward-line 2)) + (if (re-search-forward (format "^:%s:.*\n" (regexp-quote field)) nil t) + (replace-match (or text "")) + (when text + (if (re-search-forward "^$" nil t) + (replace-match text) + (insert text))))))) + +(defun pelican-mode-set-field-org-mode (field value) + "Set Org global metadata FIELD to VALUE." + ;; None of org-mode's functions I can find for setting properties + ;; operate on the global list, only a single property drawer. + (setq field (upcase field)) + (setq field + (format (if (member field '("TITLE" "DATE" "CATEGORY" "AUTHOR")) + "#+%s:" + "#+PROPERTY: %s") + field)) + (let ((text (when value (format "%s %s\n" field value)))) + (if (re-search-forward (format "^%s .*\n" (regexp-quote field)) nil t) + (replace-match (or text "")) + (when text + (if (re-search-forward "^$" nil t) + (replace-match text) + (insert text)))))) + (defun pelican-mode-set-field (field value) "Set FIELD to VALUE. FIELD may be a string or a symbol; if it is a symbol, the symbol name is used (removing a leading ':' if present). -VALUE may be any value; except for the following special values, -the unquoted printed representation of it is used: +When called from Lisp, VALUE may be any value; except for the +following special values, the unquoted printed representation of +it is used: - `now' means the current time; see `pelican-mode-timestamp'. - `slug' means the file's path relative to the document root sans extension; see `pelican-mode-default-slug'. -- nil or an empty string removes the field." +- nil or an empty string removes the field. + +The buffer must be in a format listed in `pelican-mode-formats' +for this function to work correctly." (interactive "sField: \nsValue: ") (setq value (pcase value ('now (pelican-mode-timestamp)) @@ -150,7 +219,7 @@ the unquoted printed representation of it is used: (when (symbolp field) (setq field (string-remove-prefix ":" (symbol-name field)))) (let ((set-field - (assoc-default nil pelican-mode-set-field-alist #'derived-mode-p))) + (assoc-default nil pelican-mode-formats #'derived-mode-p))) (unless set-field (error "Unsupported major mode %S" major-mode)) (save-excursion @@ -196,19 +265,9 @@ the unquoted printed representation of it is used: (when-let (file-name (file-name-sans-extension buffer-file-name)) (file-name-base file-name)))) -(defun pelican-mode-find-in-parents (file-name) - "Find FILE-NAME in the default directory or one of its parents, or nil." - (let* ((parent (expand-file-name default-directory))) - (while (and (not (file-readable-p (concat parent file-name))) - (not (string= parent (directory-file-name parent)))) - (setq parent (file-name-directory (directory-file-name parent)))) - (let ((found (concat parent file-name))) - (if (file-readable-p found) found nil)))) - (defun pelican-mode-find-root () "Return the root of the buffer's Pelican site, or nil." - (when-let (conf (pelican-mode-find-in-parents "pelicanconf.py")) - (file-name-directory conf))) + (locate-dominating-file default-directory "pelicanconf.py")) (defun pelican-make (target) "Execute TARGET in a Makefile at the root of the site." @@ -235,15 +294,20 @@ With a prefix argument ARG, enable Pelican mode if ARG is positive, and disable it otherwise. If called from Lisp, enable the mode if ARG is omitted or nil. +Pelican is a static site generator which can process a variety of +text file formats. For more information, see URL +https://blog.getpelican.com/. + When Pelican mode is enabled, additional commands are available for editing articles or pages: \\{pelican-mode-map}" :lighter " Pelican" - :keymap `((,(kbd "C-c P n") . pelican-mode-insert-auto-header) + :keymap `((,(kbd "C-c P f") . pelican-set-field) + (,(kbd "C-c P h") . pelican-make-html) + (,(kbd "C-c P n") . pelican-mode-insert-header) (,(kbd "C-c P p") . pelican-mode-publish-draft) (,(kbd "C-c P t") . pelican-mode-update-date) - (,(kbd "C-c P h") . pelican-make-html) (,(kbd "C-c P u") . pelican-make-rsync-upload))) ;;;###autoload @@ -253,6 +317,10 @@ With a prefix argument ARG, enable Pelican global mode if ARG is positive, and disable it otherwise. If called from Lisp, enable the mode if ARG is omitted or nil. +Pelican is a static site generator which can process a variety of +text file formats. For more information, see URL +https://blog.getpelican.com/. + When Pelican global mode is enabled, text files which seem to be part of a Pelican site will have `pelican-mode' automatically enabled. @@ -261,7 +329,7 @@ If you disable this, you may still enable `pelican-mode' manually or add `pelican-mode-enable-if-site' to more specific mode hooks." :global t - :group 'pelican + :group 'pelican-mode (if pelican-global-mode (add-hook 'text-mode-hook #'pelican-mode-enable-if-site) (remove-hook 'text-mode-hook #'pelican-mode-enable-if-site)))