Skip to content

Instantly share code, notes, and snippets.

@luxbock luxbock/incise.el
Last active Aug 29, 2015

What would you like to do?
;; Configure: Add org-mode-dir/lisp/ to Emacs load-path
(add-to-list 'load-path "~/.emacs.d/plugins/org-8.2.5c/lisp")
(require 'org)
(require 'ox-publish)
;; Incise uses the new org-mode Export Framework (introduced in 8.0) See more
;; information here:
;; Configure: Uncomment the proper export libraries as required
(require 'ox-html)
(require 'ox-ascii)
;; (require 'ox-latex) ;; Also used for pdf-export
;; (require 'ox-md)
;; (require 'ox-beamer)
;; (require 'ox-icalendar)
;; (require 'ox-texinfo)
;; (require 'ox-man)
;; (require 'ox-odt)
;; (require 'ox-deck)
;; Config: incise.el includes a default config that attempts to publish your
;; org-files and static content from your this-project/resources/org/ directory
;; into this/project/public/. There's a lot more functionality you can achieve
;; by customizing `org-publish-project-alist' below. For more information use
;; `C-h v "org-publish-project-alist"' and the links below:
(setq org-publish-project-alist
;; You can define as many of these components as needed create the
;; behaviour you want from Emacs / org-mode.
("incise-org" ;; This component exports org files to html
;; Configure: This is where Emacs looks for your content:
:base-directory "~/Development/clojure/incise-org-parser/resources/org/"
;; Filename suffix without the dot.
:base-extension "org"
;; Configure: The base directory where the files get published to:
:publishing-directory "~/Development/clojure/incise-org-parser/public/"
;; If t, include subdirectories. Creates new subdirectories in the
;; publishing directory is they don't exist
:recursive t
;; How we want to process the files before publishing
:publishing-function org-html-publish-to-html
;; A default setting - can be overwritten on per file basis
:headline-levels 2
;; I don't know what this does
:auto-preamble t)
("incise-static" ;; This component exports your static content
;; Configure:
:base-directory "~/Development/clojure/incise-org-parser/resources/org/"
;; :base-extension can be a regular expression
:base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf"
;; Configure:
:publishing-directory "~/Development/clojure/incise-org-parser/public/"
:recursive t
:publishing-function org-publish-attachment)
;; You can combine other components into one. This way you can publish
;; everything with one command.
:components ("incise-org" "incise-static"))))
;; If you use org-babel and your files include source blocks, Emacs will prompt
;; you for if you'd like to run the code in the source blocks, which interrupts
;; the shell-call to the Emacs process. Since you most likely publishing your
;; own org files, it is assumed that the source-blocks in your files do not
;; contain malicious code. Setting the below setting true will cause
;; incise-org-parser to not work.
(setq org-confirm-babel-evaluate nil)
[incise.parsers.core :as pc]
[incise.config :as conf]
;; [incise.parsers.parse :refer [map->Parse record-parse publish-parse?]]
;; [incise.parsers.utils :refer [meta->write-path]]
;; [incise.utils :refer [get-extension]]
[clojure.string :as str]
[ :refer [sh]]
[ :refer :all]
;; [clj-time.coerce :refer [to-date]]
;; [clj-time.local :refer [local-now]]
[taoensso.timbre :refer [info error]]))
;; config ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Structure of config -- a lot shorter than I was expecting
{:org {:emacs-cmd "emacs"
:publish-config "resources/incise.el"}}}
(def org-conf
(let [config-defaults {:emacs-cmd "emacs"
:publish-config "resources/incise.el"}
incise-default (conf/get-in [:parsers :org])]
(merge config-defaults incise-default)))
;; version check ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn get-version-info []
(let [eval-str "(message (format \"%s --- %s\" (emacs-version) (org-version)))"
sh-reply (sh "emacs" "--batch" "--load=org" "--eval" eval-str)
sh-str (if (empty? (:out sh-reply)) (:err sh-reply) (:out sh-reply))
[emacs org] (str/split sh-str #" --- ")]
{:exit-code (:exit sh-reply)
:emacs emacs
:org (str/trimr org)}))
(defn- version-check
[ver-str needed]
(let [ver (-> (re-find #"\d+\.\d+\.\d+" ver-str) (str/split #"\.") first)]
(>= (Integer. ver) needed)))
(defn emacs-version-at-least-24? []
(version-check (:emacs (get-version-info)) 24))
(defn org-version-at-least-8? []
(version-check (:org (get-version-info)) 8))
;; file exists checks ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn emacs-cmd-exists? []
(= (:exit (sh "which" (:emacs-cmd org-conf))) 0))
(defn publish-config-exists? []
(.exists (file (:publish-config org-conf))))
;; reading meta from the file ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- org-read-meta-from-org-str
(let [org-meta-map {"#+AUTHOR" :author
"#+TITLE" :title
"#+EMAIL" :email
"#+DATE" :date
"#+TAGS" :tags
"#+CATEGORY" :category
"#+PUBLISH" :publish}
headers (->> (str/split org-str #"\n")
(filter #(= (apply str (take 2 %)) "#+"))
(map str/trimr))]
(reduce (fn [m l]
(let [[title val] (str/split l #": ")]
(assoc m (get org-meta-map title) val)))
{} headers)))
(defn- remove-nil-entries [m]
(into {} (remove (comp nil? second) m)))
(defn get-org-meta [org-str]
(let [meta (remove-nil-entries (org-read-meta-from-org-str org-str))
tags (or (:tags meta) "")]
(assoc meta
:tags (str/split tags #" "))))
(defn get-org-meta-from-file [file]
(get-org-meta (slurp file)))
;; shell call ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def emacs-sh-partial
"Helper function for invoking the shell call with some of the command line
arguments fixed."
(partial sh (:emacs-cmd org-conf) "-Q" "--batch"
(str "--load=" (:publish-config org-conf))))
(defn emacs-shell-call
"Calls Emacs via and evals a sexp of elisp defined by
eval-str. The `emacs' command and the `publish-condig' are defined in
org-conf. The full command that gets executed is as follows:
<emacs-cmd> --Q --batch --load=<publish-config> --eval=<eval-str"
[& args]
{:pre [(emacs-cmd-exists?)
(apply emacs-sh-partial args)
(catch e
(println "Failed to call Emacs from shell: " (str e))
(error e))))
(defn exit-ok? [sh-out] (= (:exit sh-out) 0))
(defn publish-project
"Calls Emacs through to perform (org-publish <project>).
This publishes every file that has been modified in the :base-directory as
defined in the publish-config file"
(emacs-shell-call (str "--eval=(org-publish-project \"" project "\")")))
(defn publish-project-forced
"Calls Emacs through to to perform (org-publish <project> t) on a
project. This publishes every file in the :base-directory regardless of
whether it has been modified since the last the publish command was used.
`project' is the name of the component defined in incise.el's (or whatever
file the keyword :org-publish-config is pointing to) org-publish-project-alist
which you'd like to invoke. Different components can be used and combined to
publish different projects with different configurations. Most of
incise-org-parser's functionality comes from configuring your :publish-config
option properly."
(emacs-shell-call (str "--eval=(org-publish-project \"" project "\" t)")))
(defn publish-file
"Calls Emacs through to perform
`org-publish-current-file' on the file specified."
(emacs-shell-call (str "--visit=" (.getCanonicalPath file) " ")
"--funcall" "org-publish-current-file"))
;; making sense of the output ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn get-published-file-paths
"Takes in the output of a valid Emacs shell call, and retrieves the file paths
for the files that were written as a result of a succesful export"
(->> (str/split sh-output #"\n")
(map #(str/split % #" "))
(filter #(= (first %) "Wrote"))
(map second)))
;; parsing ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn publish-project-and-parse-with-fn
[project publish-fn]
(let [sh-out (publish-fn project)]
(when (exit-ok? sh-out)
(map #(create-parse %) (get-published-file-paths (:err sh-out))))))
(defn publish-project-and-parse
([project force?]
{:pre [(= force? :force)]}
(publish-project-and-parse-with-fn project publish-project-forced))
(publish-project-and-parse-with-fn project publish-project)))
(defn create-parse [])
;; TODO ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn everything-ok? []
(format "Unable to find %s, failed to invoke Emacs") (:emacs-cmd org-conf))
(println "incise-org-parser requires Emacs 24.x or higher")
(println "incise-org-parser requires org-mode version of 8.x or higher")
(println "Unable to find location of publish-config. Check your config-file")
:else true))
(defn org-project-parser
"Takes a `project' component string defined in the :publish-config file and
returns an org-parser."
([project force?]
{:pre [(= force? :force)]}
(when (everything-ok?)
(if force?
(publish-project-and-parse project :force)
(publish-project-and-parse project)))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.