;;; pelican-mode.el --- Minor mode for editing Pelican sites -*- lexical-binding: t -*-
;;
-;; Copyright 2013-2017 Joe Wreschnig
+;; Copyright 2013-2018 Joe Wreschnig
;;
;; Author: Joe Wreschnig <joe.wreschnig@gmail.com>
-;; Package-Version: 20170730
+;; Package-Version: 20180605.1
;; Package-Requires: ((emacs "25"))
+;; URL: https://git.korewanetadesu.com/pelican-mode.git
;; Keywords: convenience, editing
;;
;; This program is free software; you can redistribute it and/or modify
;;; Commentary:
;;
-;; pelican-mode is an Emacs minor mode for editing pages and posts in
-;; Pelican sites. Pelican is a static site generator which can
+;; pelican-mode is an Emacs minor mode for editing articles and pages
+;; 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 a major mode for the Pelican
+;; It’s intended to be used alongside a major mode for the Pelican
;; document. Currently supported formats are Markdown,
-;; reStructuredText, AsciiDoc, and Org. It also assumes you've set up
-;; Pelican with ``pelican-quickstart'' or something like it. In
+;; reStructuredText, AsciiDoc, and Org. It also assumes you’ve set up
+;; Pelican with “pelican-quickstart” or something like it. In
;; particular it expects:
;;
-;; * The existence of ``pelicanconf.py'' and ``Makefile'' in some
+;; * The existence of “pelicanconf.py” and “Makefile” in some
;; ancestor directory.
-;; * The first component of the path (e.g. ``content'') after that
+;; * The first component of the path (e.g. “content”) after that
;; ancestor is irrelevant.
-;; * If the next component is ``pages'', that indicates a page
+;; * 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'
+;; Or with ‘use-package’ and deferred loading:
+;;
+;; (use-package pelican-mode
+;; :demand :after (:any org rst markdown-mode adoc-mode)
+;; :config
+;; (pelican-global-mode))
+;;
+;; Or, register ‘pelican-mode’ or ‘pelican-mode-enable-if-site’
;; as hook functions for more direct control.
\f
(require 'seq)
(require 'subr-x)
-;; Mode Definition
-
-;;;###autoload
-(define-minor-mode pelican-mode
- "Toggle Pelican mode.
-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/.
-
-Rather than manually enabling this mode, you may wish to use
-`pelican-global-mode' or `pelican-mode-enable-if-site'.
-
-When Pelican mode is enabled, additional commands are available
-for editing articles or pages:
-
-\\{pelican-mode-map}"
- :lighter " Pelican"
- :keymap `((,(kbd "C-c P d") . pelican-mode-update-date)
- (,(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 u") . pelican-make-rsync-upload)))
-
-;;;###autoload
-(define-minor-mode pelican-global-mode
- "Toggle Pelican global mode.
-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.
-
-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-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)))
-
-;;;###autoload
-(defun pelican-mode-enable-if-site ()
- "Enable `pelican-mode' if this buffer is part of a Pelican site.
-
-Pelican sites are detected by looking for a file named `pelicanconf.py'
-in an ancestor directory."
- (when (pelican-mode-find-root)
- (pelican-mode)))
-
-\f
-
;; Customizations
-(defgroup pelican-mode nil
+(defgroup pelican nil
"Support for Pelican articles and pages.
For more information about Pelican see URL https://blog.getpelican.com/."
:group 'convenience)
+(defcustom pelican-mode-keymap-prefix (kbd "C-c =")
+ "Pelican mode keymap prefix."
+ :group 'pelican
+ :type 'string)
+
(defcustom pelican-mode-default-page-fields
'(:slug slug)
"Fields to include when creating a new page.
-See the documentation for `pelican-mode-set-field' for more information
+See the documentation for ‘pelican-mode-set-field’ for more information
about metadata fields and special values."
- :group 'pelican-mode
+ :group 'pelican
:type '(plist))
(defcustom pelican-mode-default-article-fields
'(:date now :status "draft" :slug slug)
"Fields to include when creating a new article.
-See the documentation for `pelican-mode-set-field' for more information
+See the documentation for ‘pelican-mode-set-field’ for more information
about metadata fields and special values."
- :group 'pelican-mode
+ :group 'pelican
:type '(plist))
(defcustom pelican-mode-formats
This association list maps modes to functions that take two
arguments, field and value strings."
- :group 'pelican-mode
+ :group 'pelican
:type '(alist :key-type function :value-type function))
\f
+;; Mode Definition
+
+(defvar pelican-mode-command-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "d") #'pelican-mode-update-date)
+ (define-key map (kbd "f") #'pelican-mode-set-field)
+ (define-key map (kbd "h") #'pelican-make-html)
+ (define-key map (kbd "n") #'pelican-mode-insert-header)
+ (define-key map (kbd "p") #'pelican-mode-publish)
+ (define-key map (kbd "u") #'pelican-make-rsync-upload)
+ (define-key map (kbd "g") #'pelican-make-github)
+ map)
+ "Keymap for Pelican commands after ‘pelican-mode-keymap-prefix’.")
+(fset 'pelican-mode-command-map pelican-mode-command-map)
+
+(defvar pelican-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map pelican-mode-keymap-prefix
+ 'pelican-mode-command-map)
+ map)
+ "Keymap for Pelican mode.")
+
+;;;###autoload
+(define-minor-mode pelican-mode
+ "Toggle Pelican mode.
+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/.
+
+Rather than manually enabling this mode, you may wish to use
+‘pelican-global-mode’ or ‘pelican-mode-enable-if-site’.
+
+When Pelican mode is enabled, additional commands are available
+for editing articles or pages:
+
+\\{pelican-mode-map}"
+ :group 'pelican
+ :keymap pelican-mode-map
+ :lighter " Pelican")
+
+;;;###autoload
+(define-globalized-minor-mode pelican-global-mode pelican-mode
+ (lambda ()
+ (when (derived-mode-p #'text-mode)
+ (pelican-mode-enable-if-site)))
+ :group 'pelican
+ :require 'pelican-mode)
+
+;;;###autoload
+(defun pelican-mode-enable-if-site ()
+ "Enable ‘pelican-mode’ if this buffer is part of a Pelican site.
+
+Pelican sites are detected by looking for a file named
+“pelicanconf.py” in an ancestor directory."
+ (when (pelican-mode-find-root)
+ (pelican-mode)))
+
+\f
+
;; User Commands
(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).
+symbol name is used (removing a leading “:” if present).
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.
+- ‘now’ means the current time.
-- `slug' means the file's path relative to the document root sans
- extension; see `pelican-mode-default-slug'.
+- ‘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.
-The buffer must be in a format listed in `pelican-mode-formats'
+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
(pelican-mode-set-field :title title))
(defun pelican-mode-update-date (&optional original)
- "Update the document's modification date.
+ "Update the document’s modification date.
If ORIGINAL is non-nil, the publication date is updated rather
than the modification date."
(interactive "P")
(pelican-mode-set-field (if original :date :modified) 'now))
-(defun pelican-mode-publish-draft ()
- "Remove draft status from a Pelican article."
+(defun pelican-mode-publish ()
+ "Remove draft or hidden status from a Pelican article."
(interactive)
(pelican-mode-remove-field :status)
(pelican-mode-update-date :date))
-(defun pelican-mode-insert-draft-article-header (title tags)
- "Insert a Pelican header for a draft with a TITLE and TAGS."
+(defun pelican-mode-insert-article-header (title tags)
+ "Insert a Pelican header for an article with a TITLE and TAGS."
(interactive "sArticle title: \nsTags: ")
- (apply #'pelican-mode-set-fields
- `(:title ,title
- ,@pelican-mode-default-article-fields
- :tags ,tags)))
+ (save-excursion
+ (goto-char 0)
+ (insert "\n")
+ (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.
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)))
+ (save-excursion
+ (goto-char 0)
+ (insert "\n")
+ (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."
(call-interactively
(if (pelican-mode-page-p)
#'pelican-mode-insert-page-header
- #'pelican-mode-insert-draft-article-header)))
+ #'pelican-mode-insert-article-header)))
(defun pelican-make (target)
"Execute TARGET in a Makefile at the root of the site."
(interactive "sMake Pelican target: ")
- (if-let (default-directory (pelican-mode-find-root))
- (compilation-start (format "make %s" target)
- nil (lambda (_) "*pelican*"))
- (user-error "No Pelican site root could be found")))
+ (let ((default-directory (pelican-mode-find-root)))
+ (if default-directory
+ (compilation-start (format "make %s" target)
+ nil (lambda (_) "*pelican*"))
+ (user-error "No Pelican site root could be found"))))
(defun pelican-make-html ()
"Generate HTML via a Makefile at the root of the site."
(interactive)
(pelican-make "rsync_upload"))
+(defun pelican-make-github ()
+ "Upload to GitHub Pages via a Makefile at the root of the site."
+ (interactive)
+ (pelican-make "github"))
+
\f
(defun pelican-mode-set-fields (&rest fields)
(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
+ ;; 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
(defun pelican-mode-page-p ()
"Return non-nil the current buffer is a Pelican page."
- (when-let (pelican-mode-base (pelican-mode-find-root))
- (let* ((relative (file-relative-name buffer-file-name pelican-mode-base))
- (components (split-string relative "/")))
- (equal "pages" (cadr components)))))
+ (string-match-p
+ "^[^/]+/pages/"
+ (file-relative-name
+ (abbreviate-file-name (or (buffer-file-name) (buffer-name)))
+ (pelican-mode-find-root))))
(defun pelican-mode-default-slug ()
- "Generate a Pelican article/page slug for the current buffer."
- (if-let ((pelican-mode-base (pelican-mode-find-root))
- (file-name (file-name-sans-extension buffer-file-name)))
- (let* ((relative (file-relative-name file-name pelican-mode-base))
- (components (cdr (split-string relative "/")))
- (components (if (string= "pages" (car components))
- (cdr components) components)))
- (mapconcat 'identity components "/"))
- (when-let (file-name (file-name-sans-extension buffer-file-name))
- (file-name-base file-name))))
+ "Generate a Pelican slug for the current buffer."
+ (file-name-sans-extension
+ (replace-regexp-in-string
+ "^[^/]+/\\(?:pages/\\)?" ""
+ (file-relative-name
+ (abbreviate-file-name (or (buffer-file-name) (buffer-name)))
+ (pelican-mode-find-root)))))
(defun pelican-mode-find-root ()
- "Return the root of the buffer's Pelican site, or nil."
+ "Return the root of the buffer’s Pelican site, or nil."
(locate-dominating-file default-directory "pelicanconf.py"))
(provide 'pelican-mode)
;;; pelican-mode.el ends here
-
-\f
-
-;; Local Variables:
-;; sentence-end-double-space: t
-;; End: