--- /dev/null
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
--- /dev/null
+# pelican-mode
+
+pelican-mode is a minor mode for editing pages and posts in [Pelican]
+sites. 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 static page
+ rather than a dated post.
+
+It also enforces some parts of my preferred Pelican configuration:
+
+ * Categories are never provided (you can have one if you want, but
+ the default interactive commands don't provide one).
+ * Tags are always provided.
+ * Slugs are explicit, and include nested subdirectories.
+
+## Quick Guide
+
+* `C-x p n` - Insert a post or page header
+* `C-x p p` - Remove draft status from a post (i.e. publish it)
+* `C-x p t` - Update the date field in a post/page header
+* `C-x p h` - Generate HTML output for a site (equivalent to `make html`)
+* `C-x p u` - Upload a site using rsync (equivalent to `make rsync_upload`)
+
+## Troubleshooting
+
+If the commands which invoke `make` can find the Makefile but can't
+find `pelican`, your `exec-path` may not be set right. Try out
+[exec-path-from-shell].
+
+## License
+
+This code is released into the public domain via the
+[CC0 Public Domain Dedication][0].
+
+ [Pelican]: http://getpelican.com/
+ [markdown-mode]: http://jblevins.org/projects/markdown-mode/
+ [rst-mode]: http://docutils.sourceforge.net/docs/user/emacs.html
+ [exec-path-from-shell]: https://github.com/purcell/exec-path-from-shell
+ [0]: http://creativecommons.org/publicdomain/zero/1.0/legalcode
--- /dev/null
+;;; pelican-mode.el --- Minor mode for editing pages and posts in Pelican sites
+;;
+;; Author: Joe Wreschnig
+;; This code is released into the public domain.
+
+;;; 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.
+
+;;; Code:
+
+(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)))
+ (save-excursion
+ (goto-char 0)
+ (insert (funcall header title nil hidden nil nil slug)))))
+
+(defun pelican-insert-header ()
+ "Insert a Pelican header for a page or post."
+ (interactive)
+ (call-interactively (if (pelican-is-page)
+ 'pelican-insert-page-header
+ 'pelican-insert-draft-post-header)))
+
+(defun pelican-update-date ()
+ "Update a Pelican date header."
+ (interactive)
+ (save-excursion
+ (goto-char 0)
+ (let* ((field (if (pelican-is-markdown) "Date" ":date"))
+ (re (format "^%s: [-0-9 :]+\n" field))
+ (date (pelican-timestamp-now)))
+ (if (re-search-forward re nil t)
+ (replace-match (format "%s: %s\n" field date))
+ (message "This doesn't look like a Pelican page.")))))
+
+(defun pelican-publish-draft ()
+ "Remove draft status from a Pelican post."
+ (interactive)
+ (save-excursion
+ (goto-char 0)
+ (let* ((field (if (pelican-is-markdown) "Status" ":status"))
+ (re (format "^%s: draft\n" field)))
+ (if (re-search-forward re nil t)
+ (progn
+ (replace-match (format ""))
+ (pelican-update-date))
+ (message "This doesn't look like a Pelican draft.")))))
+
+(defun pelican-is-page ()
+ "Guess the current buffer is a Pelican page (vs. a post or neither)."
+ (let ((pelican-base (pelican-find-root)))
+ (if pelican-base
+ (let* ((relative (file-relative-name buffer-file-name pelican-base))
+ (components (split-string relative "/")))
+ (string= "pages" (car (cdr components)))))))
+
+(defun pelican-default-slug ()
+ "Generate a Pelican post/page slug for the current buffer."
+ (let ((pelican-base (pelican-find-root))
+ (file-name (file-name-sans-extension buffer-file-name)))
+ (if pelican-base
+ (let* ((relative (file-relative-name file-name pelican-base))
+ (components (cdr (split-string relative "/")))
+ (components (if (string= "pages" (car components))
+ (cdr components) components)))
+ (mapconcat 'identity components "/"))
+ (format "%s/%s"
+ (file-name-nondirectory
+ (directory-file-name
+ (file-name-directory file-name)))
+ (file-name-base file-name)))))
+
+(defun pelican-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-find-root ()
+ "Return the root of the buffer's Pelican site, or nil."
+ (let ((conf (pelican-find-in-parents "pelicanconf.py")))
+ (if conf (file-name-directory conf))))
+
+(defun pelican-is-in-site ()
+ "Check if this buffer is under a Pelican site."
+ (not (not (pelican-find-root))))
+
+(defun pelican-enable-if-site ()
+ "Enable `pelican-mode' if this buffer is under a Pelican site."
+ (if (pelican-is-in-site)
+ (pelican-mode 1)))
+
+(defun pelican-make (target)
+ "Execute TARGET in a Makefile at the root of the site."
+ (interactive "sMake Pelican target: ")
+ (let ((default-directory (pelican-find-root)))
+ (if default-directory
+ (let ((output (get-buffer-create "*Pelican Output*")))
+ (display-buffer output)
+ (pop-to-buffer output)
+ (compilation-mode)
+ (start-process "Pelican Makefile" output "make" target))
+ (message "This doesn't look like a Pelican site."))))
+
+(defun pelican-make-html ()
+ "Generate HTML via a Makefile at the root of the site."
+ (interactive)
+ (pelican-make "html"))
+
+(defun pelican-make-rsync-upload ()
+ "Upload with rsync via a Makefile at the root of the site."
+ (interactive)
+ (pelican-make "rsync_upload"))
+
+(defconst pelican-keymap (make-sparse-keymap)
+ "The default keymap used in Pelican mode.")
+(define-key pelican-keymap [?\C-x ?p ?n]
+ 'pelican-insert-header)
+(define-key pelican-keymap [?\C-x ?p ?p]
+ 'pelican-publish-draft)
+(define-key pelican-keymap [?\C-x ?p ?t]
+ 'pelican-update-date)
+(define-key pelican-keymap [?\C-x ?p ?h]
+ 'pelican-make-html)
+(define-key pelican-keymap [?\C-x ?p ?u]
+ 'pelican-make-rsync-upload)
+
+(define-minor-mode pelican-mode
+ "Toggle Pelican mode.
+
+Interactively with no argument, this command toggles the mode.
+to show buffer size and position in mode-line.
+"
+ :init-value nil
+ :lighter " Pelican"
+ :keymap pelican-keymap
+ :group 'pelican
+ )
+
+(add-hook 'markdown-mode-hook 'pelican-enable-if-site)
+(add-hook 'rst-mode-hook 'pelican-enable-if-site)
+
+(provide 'pelican-mode)
+
+;;; pelican-mode.el ends here