Browse Source

Merge pull request #637 from andreas-h/org_reader_metadata

Org reader metadata
Justin Mayer 9 years ago
parent
commit
88484c3001
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

@@ -138,6 +138,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)