Skip to content

Instantly share code, notes, and snippets.

@titaniumbones
Last active October 22, 2018 19:53
Show Gist options
  • Save titaniumbones/c0c171e4df8a6ff5f0f564b8a655c079 to your computer and use it in GitHub Desktop.
Save titaniumbones/c0c171e4df8a6ff5f0f564b8a655c079 to your computer and use it in GitHub Desktop.
ox-slack -- slack exporter for emacs org-mode
;;; ox-slack.el --- SLack Exporter for org-mode
;; Copyright (C) 2018 Matt Price
;; Author: Matt Price
;; Keywords: org, slack
;; Modeled on various other derived backends including 'ox-gfm by Lars Trier
;; This file is not part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This library implements a Slack backend for the Org
;; exporter, based on the `ascii' back-end.
;;; Code:
(require 'ox-gfm)
(org-export-define-derived-backend 'slack 'gfm
:translate-alist
'(
(bold . org-slack-bold)
(code . org-slack-code)
(headline . org-slack-headline)
(inner-template . org-slack-inner-template)
(italic . org-slack-italic)
(link . org-slack-link)
(plain-text . org-slack-plain-text)
(src-block . org-slack-src-block)
(strike-through . org-slack-strike-through)
(timestamp . org-slack-timestamp)
) )
;; timestamp
(defun org-slack-timestamp (timestamp contents info)
"Transcode TIMESTAMP element into Slck format. CONTENTS is the timestamp contents.
INFO is a plist used as a ocmmunications channel."
(let*
((value (org-html-plain-text (org-timestamp-translate timestamp) info)))
value))
;; headline
(defun org-slack-headline (headline contents info)
"Transcode HEADLINE element into Markdown format.
CONTENTS is the headline contents. INFO is a plist used as
a communication channel."
(unless (org-element-property :footnote-section-p headline)
(let* ((level (org-export-get-relative-level headline info))
(title (org-export-data (org-element-property :title headline) info))
(todo (and (plist-get info :with-todo-keywords)
(let ((todo (org-element-property :todo-keyword
headline)))
(and todo (concat (org-export-data todo info) " ")))))
(tags (and (plist-get info :with-tags)
(let ((tag-list (org-export-get-tags headline info)))
(and tag-list
(concat " " (org-make-tag-string tag-list))))))
(priority
(and (plist-get info :with-priority)
(let ((char (org-element-property :priority headline)))
(and char (format "[#%c] " char)))))
;; Headline text without tags.
(heading (concat todo priority title)))
(format "*%s*\n\n%s" title contents)
)))
(defun org-slack-link (link contents info)
"Transcode LINE-BREAK object into Markdown format.
CONTENTS is the link's description. INFO is a plist used as
a communication channel."
(let ((link-org-files-as-md
(lambda (raw-path)
;; Treat links to `file.org' as links to `file.md'.
(if (string= ".org" (downcase (file-name-extension raw-path ".")))
(concat (file-name-sans-extension raw-path) ".md")
raw-path)))
(type (org-element-property :type link)))
(cond
;; Link type is handled by a special function.
((org-export-custom-protocol-maybe link contents 'md))
((member type '("custom-id" "id" "fuzzy"))
(let ((destination (if (string= type "fuzzy")
(org-export-resolve-fuzzy-link link info)
(org-export-resolve-id-link link info))))
(pcase (org-element-type destination)
(`plain-text ; External file.
(let ((path (funcall link-org-files-as-md destination)))
(if (not contents) (format "%s>" path)
(format "[%s](%s)" contents path))))
(`headline
(format
"[%s](#%s)"
;; Description.
(cond ((org-string-nw-p contents))
((org-export-numbered-headline-p destination info)
(mapconcat #'number-to-string
(org-export-get-headline-number destination info)
"."))
(t (org-export-data (org-element-property :title destination)
info)))
;; Reference.
(or (org-element-property :CUSTOM_ID destination)
(org-export-get-reference destination info))))
(_
(let ((description
(or (org-string-nw-p contents)
(let ((number (org-export-get-ordinal destination info)))
(cond
((not number) nil)
((atom number) (number-to-string number))
(t (mapconcat #'number-to-string number ".")))))))
(when description
(format "[%s](#%s)"
description
(org-export-get-reference destination info))))))))
((org-export-inline-image-p link org-html-inline-image-rules)
(let ((path (let ((raw-path (org-element-property :path link)))
(cond ((not (equal "file" type)) (concat type ":" raw-path))
((not (file-name-absolute-p raw-path)) raw-path)
(t (expand-file-name raw-path)))))
(caption (org-export-data
(org-export-get-caption
(org-export-get-parent-element link)) info)))
(format "![img](%s)"
(if (not (org-string-nw-p caption)) path
(format "%s \"%s\"" path caption)))))
((string= type "coderef")
(let ((ref (org-element-property :path link)))
(format (org-export-get-coderef-format ref contents)
(org-export-resolve-coderef ref info))))
((equal type "radio") contents)
(t (let* ((raw-path (org-element-property :path link))
(path
(cond
((member type '("http" "https" "ftp" "mailto"))
(concat type ":" raw-path))
((string= type "file")
(org-export-file-uri (funcall link-org-files-as-md raw-path)))
(t raw-path))))
(if (not contents) (format "%s" path)
(format "*%s* (%s)" contents path)))))))
(defun org-slack-verbatim (_verbatim contents _info)
"Transcode VERBATIM from Org to Slack.
CONTENTS is the text with bold markup. INFO is a plist holding
contextual information."
(format "`%s`" contents))
(defun org-slack-code (code _contents info)
"Return a CODE object from Org to SLACK.
CONTENTS is nil. INFO is a plist holding contextual
information."
(format "`%s`"
(org-element-property :value code)))
;;;; Italic
(defun org-slack-italic (_italic contents _info)
"Transcode italic from Org to SLACK.
CONTENTS is the text with italic markup. INFO is a plist holding
contextual information."
(format "_%s_" contents))
;;; Bold
(defun org-slack-bold (_bold contents _info)
"Transcode bold from Org to SLACK.
CONTENTS is the text with bold markup. INFO is a plist holding
contextual information."
(format "*%s*" contents))
;;;; Strike-through
(defun org-slack-strike-through (_strike-through contents _info)
"Transcode STRIKE-THROUGH from Org to SLACK.
CONTENTS is text with strike-through markup. INFO is a plist
holding contextual information."
(format "~%s~" contents))
(defun org-slack-inline-src-block (inline-src-block _contents info)
"Transcode an INLINE-SRC-BLOCK element from Org to SLACK.
CONTENTS holds the contents of the item. INFO is a plist holding
contextual information."
(format "`%s`"
(org-element-property :value inline-src-block)))
;;;; Src Block
(defun org-slack-src-block (src-block contents info)
"Transcode SRC-BLOCK element into Github Flavored Markdown
format. CONTENTS is nil. INFO is a plist used as a communication
channel."
(let* ((lang (org-element-property :language src-block))
(code (org-export-format-code-default src-block info))
(prefix (concat "```" lang "\n"))
(suffix "```"))
(concat prefix code suffix)))
;;;; Quote Block
(defun org-slack-quote-block (_quote-block contents info)
"Transcode a QUOTE-BLOCK element from Org to SLACK.
CONTENTS holds the contents of the block. INFO is a plist
holding contextual information."
(org-slack--indent-string contents (plist-get info :slack-quote-margin)))
(defun org-slack-inner-template (contents info)
"Return body of document after converting it to Markdown syntax.
CONTENTS is the transcoded contents string. INFO is a plist
holding export options."
;; Make sure CONTENTS is separated from table of contents and
;; footnotes with at least a blank line.
(concat
;; Table of contents.
;; (let ((depth (plist-get info :with-toc)))
;; (when depth
;; (concat (org-md--build-toc info (and (wholenump depth) depth)) "\n")))
;; Document contents.
contents
"\n"
;; Footnotes section.
(org-md--footnote-section info)))
;;;; Plain text
(defun org-slack-plain-text (text info)
"Transcode a TEXT string into Markdown format.
TEXT is the string to transcode. INFO is a plist holding
contextual information."
;; (when (plist-get info :with-smart-quotes)
;; (setq text (org-export-activate-smart-quotes text :html info)))
;; The below series of replacements in `text' is order sensitive.
;; Protect `, *, _, and \
;; (setq text (replace-regexp-in-string "[`*_\\]" "\\\\\\&" text))
;; Protect ambiguous #. This will protect # at the beginning of
;; a line, but not at the beginning of a paragraph. See
;; `org-md-paragraph'.
(setq text (replace-regexp-in-string "\n#" "\n\\\\#" text))
;; Protect ambiguous !
(setq text (replace-regexp-in-string "\\(!\\)\\[" "\\\\!" text nil nil 1))
;; ;; Handle special strings, if required.
;; (when (plist-get info :with-special-strings)
;; (setq text (org-html-convert-special-strings text)))
;; Handle break preservation, if required.
(when (plist-get info :preserve-breaks)
(setq text (replace-regexp-in-string "[ \t]*\n" " \n" text)))
;; Return value.
text)
;;; End-user functions
;;;###autoload
(defun org-slack-export-as-slack
(&optional async subtreep visible-only body-only ext-plist)
"Export current buffer to a text buffer.
If narrowing is active in the current buffer, only export its
narrowed part.
If a region is active, export that region.
A non-nil optional argument ASYNC means the process should happen
asynchronously. The resulting buffer should be accessible
through the `org-export-stack' interface.
When optional argument SUBTREEP is non-nil, export the sub-tree
at point, extracting information from the headline properties
first.
When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.
When optional argument BODY-ONLY is non-nil, strip title and
table of contents from output.
EXT-PLIST, when provided, is a property list with external
parameters overriding Org default settings, but still inferior to
file-local settings.
Export is done in a buffer named \"*Org SLACK Export*\", which
will be displayed when `org-export-show-temporary-export-buffer'
is non-nil."
(interactive)
(org-export-to-buffer 'slack "*Org SLACK Export*"
async subtreep visible-only body-only ext-plist (lambda () (text-mode))))
;;;###autoload
(defun org-slack-export-to-slack
(&optional async subtreep visible-only body-only ext-plist)
"Export current buffer to a text file.
If narrowing is active in the current buffer, only export its
narrowed part.
If a region is active, export that region.
A non-nil optional argument ASYNC means the process should happen
asynchronously. The resulting file should be accessible through
the `org-export-stack' interface.
When optional argument SUBTREEP is non-nil, export the sub-tree
at point, extracting information from the headline properties
first.
When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.
When optional argument BODY-ONLY is non-nil, strip title and
table of contents from output.
EXT-PLIST, when provided, is a property list with external
parameters overriding Org default settings, but still inferior to
file-local settings.
Return output file's name."
(interactive)
(let ((file (org-export-output-file-name ".txt" subtreep)))
(org-export-to-file 'slack file
async subtreep visible-only body-only ext-plist)))
(provide 'ox-slack)
;; Local variables:
;; generated-autoload-file: "org-loaddefs.el"
;; coding: utf-8
;; End:
;;; ox-slack.el ends here
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment