-(defun pelican-timestamp-now ()
- "Generate a Pelican-compatible timestamp."
- (format-time-string "%Y-%m-%d %H:%M"))
-
-(defun pelican-is-markdown ()
- "Check if the buffer is likely using Markdown."
- (eq major-mode 'markdown-mode))
-
-(defun pelican-field (name value)
- "Helper to format a field NAME and VALUE."
- (if value (format "%s: %s\n" name value) ""))
-
-(defun pelican-markdown-header (title date status category tags slug)
- "Generate a Pelican Markdown header.
-
-All parameters but TITLE may be nil to omit them. DATE may be a
-string or 't to use the current date and time."
- (let ((title (format "Title: %s\n" title))
- (status (pelican-field "Status" status))
- (category (pelican-field "Category" category))
- (tags (pelican-field "Tags" tags))
- (slug (pelican-field "Slug" slug))
- (date (if date (format "Date: %s\n"
- (if (stringp date) date
- (pelican-timestamp-now)))
- "")))
- (concat title date status tags category slug "\n")))
-
-(defun pelican-rst-header (title date status category tags slug)
- "Generate a Pelican reStructuredText header.
-
-All parameters but TITLE may be nil to omit them. DATE may be a
-string or 't to use the current date and time."
- (let ((title (format "%s\n%s\n\n" title
- (make-string (string-width title) ?#)))
- (status (pelican-field ":status" status))
- (category (pelican-field ":category" category))
- (tags (pelican-field ":tags" tags))
- (slug (pelican-field ":slug" slug))
- (date (if date (format ":date: %s\n"
- (if (stringp date) date
- (pelican-timestamp-now)))
- "")))
- (concat title date status tags category slug "\n")))
-
-(defun pelican-insert-draft-post-header (title tags)
- "Insert a Pelican header for a draft post."
- (interactive "sPost title: \nsTags: ")
- (let ((slug (pelican-default-slug))
- (header (if (pelican-is-markdown)
- 'pelican-markdown-header 'pelican-rst-header)))
- (save-excursion
- (goto-char 0)
- (insert (funcall header title 't "draft" nil tags slug)))))
-
-(defun pelican-insert-page-header (title hidden)
- "Insert a Pelican header for a page."
- (interactive
- (list (read-string "Page title: ")
- (y-or-n-p "Hidden? ")))
- (let ((slug (pelican-default-slug))
- (hidden (if hidden "hidden" nil))
- (header (if (pelican-is-markdown)
- 'pelican-markdown-header 'pelican-rst-header)))
+(require 'seq)
+(require 'subr-x)
+
+;; Customizations
+
+(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 P")
+ "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
+about metadata fields and special values."
+ :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
+about metadata fields and special values."
+ :group 'pelican
+ :type '(plist))
+
+(defcustom pelican-mode-formats
+ '((adoc-mode . pelican-mode-set-field-adoc-mode)
+ (markdown-mode . pelican-mode-set-field-markdown-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
+ :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)
+ 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).
+
+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.
+
+- `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'
+for this function to work correctly."
+ (interactive "sField: \nsValue: ")
+ (setq value (pcase value
+ ('now (format-time-string "%Y-%m-%d %H:%M"))
+ ('slug (pelican-mode-default-slug))
+ ('"" nil)
+ (_ value)))
+ (when (symbolp field)
+ (setq field (string-remove-prefix ":" (symbol-name field))))
+ (let ((set-field
+ (assoc-default nil pelican-mode-formats #'derived-mode-p)))
+ (unless set-field
+ (error "Unsupported major mode %S" major-mode))