Browse Source

[org-reader] read metadata from Org files

Read all metadata fields mentioned in the Pelican docs from Org files.
TITLE, DATE, CATEGORY, AUTHOR, LANGUAGE can be specified directly, and
the the others (summary, slug, modified, tags, save_as) have to be given
as Org properties, e.g., #+PROPERTY: TAGS list, of, tags

Make Python code PEP8 compliant
Andreas Hilboll 8 years ago
parent
commit
0766c0fec5
4 changed files with 125 additions and 28 deletions
  1. 2 0
      Readme.rst
  2. 24 7
      org_reader/README.md
  3. 52 7
      org_reader/org_reader.el
  4. 47 14
      org_reader/org_reader.py

+ 2 - 0
Readme.rst

@@ -126,6 +126,8 @@ Open graph                Generates Open Graph tags for your articles
 
 Optimize images           Applies lossless compression on JPEG and PNG images
 
+Org Reader                Create posts via Emacs Orgmode files
+
 Page View                 Pull page view count from Google Analytics.
 
 PDF generator             Automatically exports articles and pages as PDF files

+ 24 - 7
org_reader/README.md

@@ -1,6 +1,6 @@
 # Org Reader
 
-Publish Emacs Org files alongside the rest of your website or blag.
+Publish Emacs Org files alongside the rest of your website or blog.
 
 - `ORG_READER_EMACS_LOCATION`: Required. Location of Emacs binary.
 
@@ -11,12 +11,29 @@ Publish Emacs Org files alongside the rest of your website or blag.
   can ignore this variable.
 
 - `ORG_READER_BACKEND`: Optional. A custom backend to provide to Org. Defaults
-  to 'html.
+  to `'html`.
 
-To provide metadata to Pelican, provide the following header in your Org file:
+To provide metadata to Pelican, the following properties can be defined in
+the org file's header:
 
-	#+TITLE: The Title Of This BlogPost
-	#+DATE: <2001-01-01>
-	#+CATEGORY: comma, separated, list, of, tags
+    #+TITLE: The Title Of This BlogPost
+    #+DATE: 2001-01-01
+    #+CATEGORY: blog-category
+    #+AUTHOR: My Name
+    #+LANGUAGE: en
+    #+PROPERTY: SUMMARY hello, this is the description
+    #+PROPERTY: SLUG test_slug
+    #+PROPERTY: MODIFIED [2015-12-29 Di]
+    #+PROPERTY: TAGS my, first, tags
+    #+PROPERTY: SAVE_AS alternative_filename.html
 
-The slug is automatically the filename of the Org file.
+- The `TITLE` is the only mandatory header property
+- Timestamps (`DATE` and `MODIFIED`) are optional and can be either a string
+  of `%Y-%m-%d` or an org timestamp
+- The property names (`SUMMARY`, `SLUG`, `MODIFIED`, `TAGS`, `SAVE_AS`) can
+  be either lower-case or upper-case
+- The slug is automatically the filename of the Org file, if not explicitly
+  specified
+- It is not possible to pass an empty property to Pelican.  For this plugin,
+  it makes no difference if a property is present in the Org file and left
+  empty, or if it is not defined at all.

+ 52 - 7
org_reader/org_reader.el

@@ -3,12 +3,57 @@
 (defun org->pelican (filename backend)
   (progn
     (save-excursion
+      ; open org file
       (find-file filename)
-      (let ((properties (org-export-get-environment)))
-        (princ (json-encode 
-                (list 
-                 :date (org-timestamp-format (car (plist-get properties :date)) "%Y-%m-%d")
-                 :author (substring-no-properties (car (plist-get properties :author)))
-                 :category (cdr (assoc "CATEGORY" org-file-properties))
+
+      ; pre-process some metadata
+      (let (; extract org export properties
+            (org-export-env (org-export-get-environment))
+            ; convert MODIFIED prop to string
+            (modifiedstr (cdr (assoc-string "MODIFIED" org-file-properties t)))
+            ; prepare date property
+            (dateobj (car (plist-get (org-export-get-environment) ':date)))
+            )
+
+        ; check if #+TITLE: is given and give sensible error message if not
+        (if (symbolp (car (plist-get org-export-env :title)))
+            (error "Each page/article must have a #+TITLE: property"))
+
+        ; construct the JSON object
+        (princ (json-encode
+                (list
+                 ; org export environment
+                 :title (substring-no-properties
+                         (car (plist-get org-export-env :title)))
+                 ; if #+DATE is not given, dateobj is nil
+                 ; if #+DATE is a %Y-%m-%d string, dateobj is a string,
+                 ; and otherwise we assume #+DATE is a org timestamp
+                 :date (if (symbolp dateobj)
+                           ""
+                         (if (stringp dateobj)
+                             (org-read-date nil nil dateobj nil)
+                           (org-timestamp-format dateobj "%Y-%m-%d")))
+
+                 :author (substring-no-properties
+                          (car (plist-get org-export-env ':author)))
+                 :language (plist-get org-export-env ':language)
+
+                 ; org file properties
+                 :category (cdr (assoc-string "CATEGORY" org-file-properties t))
+
+                 ; custom org file properties, defined as #+PROPERTY: NAME ARG
+                 :save_as (cdr (assoc-string "SAVE_AS" org-file-properties t))
+                 :tags (cdr (assoc-string "TAGS" org-file-properties t))
+                 :summary (cdr (assoc-string "SUMMARY" org-file-properties t))
+                 :slug (cdr (assoc-string "SLUG" org-file-properties t))
+                 :modified (if (stringp modifiedstr)
+                               (org-read-date nil nil modifiedstr nil)
+                             "")
                  :post (org-export-as backend nil nil t)
-                 :title (substring-no-properties (car (plist-get properties :title))))))))))
+                 )
+                )
+               )
+        )
+      )
+    )
+  )

+ 47 - 14
org_reader/org_reader.py

@@ -2,7 +2,7 @@
 Org Reader
 ==========
 
-Version 1.0.
+Version 1.1.
 
 Relevant Pelican settings:
 
@@ -16,13 +16,31 @@ Relevant Pelican settings:
 - ORG_READER_BACKEND: Optional. A custom backend to provide to Org. Defaults
   to 'html.
 
-To provide metadata to Pelican, provide the following header in your Org file:
+To provide metadata to Pelican, the following properties can be defined in
+the org file's header:
 
 #+TITLE: The Title Of This BlogPost
 #+DATE: 2001-01-01
-#+CATEGORY: comma, separated, list, of, tags
+#+CATEGORY: blog-category
+#+AUTHOR: My Name
+#+LANGUAGE: en
+#+PROPERTY: SUMMARY hello, this is the description
+#+PROPERTY: SLUG test_slug
+#+PROPERTY: MODIFIED [2015-12-29 Di]
+#+PROPERTY: TAGS my, first, tags
+#+PROPERTY: SAVE_AS alternative_filename.html
+
+- The TITLE is the only mandatory header property
+- Timestamps (DATE and MODIFIED) are optional and can be either a string of
+  %Y-%m-%d or an org timestamp
+- The property names (SUMMARY, SLUG, MODIFIED, TAGS, SAVE_AS) can be either
+  lower-case or upper-case
+- The slug is automatically the filename of the Org file, if not explicitly
+  specified
+- It is not possible to pass an empty property to Pelican.  For this plugin,
+  it makes no difference if a property is present in the Org file and left
+  empty, or if it is not defined at all.
 
-The slug is automatically the filename of the Org file.
 """
 import os
 import json
@@ -30,16 +48,16 @@ import logging
 import subprocess
 from pelican import readers
 from pelican import signals
-from pelican import settings
 
 
 ELISP = os.path.join(os.path.dirname(__file__), 'org_reader.el')
 LOG = logging.getLogger(__name__)
 
+
 class OrgReader(readers.BaseReader):
     enabled = True
 
-    EMACS_ARGS = ["--batch"]
+    EMACS_ARGS = ["-Q", "--batch"]
     ELISP_EXEC = "(org->pelican \"{0}\" {1})"
 
     file_extensions = ['org']
@@ -71,22 +89,37 @@ class OrgReader(readers.BaseReader):
         json_result = subprocess.check_output(cmd, universal_newlines=True)
         json_output = json.loads(json_result)
 
-        slug, e = os.path.splitext(os.path.basename(filename))
-
-        metadata = {'title': json_output['title'],
-                    'tags': json_output['category'] or '',
-                    'slug': slug,
-                    'author': json_output['author'],
-                    'date': json_output['date']}
+        # get default slug from .org filename
+        default_slug, _ = os.path.splitext(os.path.basename(filename))
+
+        metadata = {'title': json_output['title'] or '',
+                    'date': json_output['date'] or '',
+                    'author': json_output['author'] or '',
+                    'lang': json_output['language'] or '',
+                    'category': json_output['category'] or '',
+                    'slug': json_output['slug'] or default_slug,
+                    'modified': json_output['modified'] or '',
+                    'tags': json_output['tags'] or '',
+                    'save_as': json_output['save_as'] or '',
+                    'summary': json_output['summary'] or ''}
+
+        # remove empty strings when necessary
+        for key in ['save_as', 'modified', 'lang', 'summary']:
+            if not metadata[key]:
+                metadata.pop(key)
 
         parsed = {}
         for key, value in metadata.items():
             parsed[key] = self.process_metadata(key, value)
 
-        return json_output['post'], parsed
+        content = json_output['post']
+
+        return content, parsed
+
 
 def add_reader(readers):
     readers.reader_classes['org'] = OrgReader
 
+
 def register():
     signals.readers_init.connect(add_reader)