dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

ob-jq.el (5718B)


      1 ;;; ob-jq.el --- org-babel functions for jq scripts  -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2015 Bjarte Johansen
      4 
      5 ;; Author: Bjarte Johansen
      6 ;; Keywords: literate programming, reproducible research
      7 ;; Homepage: http://www.github.com/ljos/jq-mode
      8 ;; Version: 0.1.0
      9 
     10 ;;; License:
     11 
     12 ;; This program is free software; you can redistribute it and/or modify
     13 ;; it under the terms of the GNU General Public License as published by
     14 ;; the Free Software Foundation; either version 3, or (at your option)
     15 ;; any later version.
     16 ;;
     17 ;; This program is distributed in the hope that it will be useful,
     18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     20 ;; GNU General Public License for more details.
     21 ;;
     22 ;; You should have received a copy of the GNU General Public License
     23 ;; along with jq-mode. If not, see <http://www.gnu.org/licenses/>.
     24 
     25 ;;; Commentary:
     26 
     27 ;; Provides a way to evaluate jq scripts in org-mode.
     28 
     29 ;;; Usage:
     30 
     31 ;; Add to your Emacs config:
     32 
     33 ;; (org-babel-do-load-languages
     34 ;;  'org-babel-load-languages
     35 ;;  '((jq . t)))
     36 
     37 ;;; Code:
     38 (require 'ob)
     39 (require 'jq-mode)
     40 (require 'json)
     41 
     42 (defvar org-babel-jq-command "jq"
     43   "Name of the jq executable command.")
     44 
     45 (defvar org-babel-tangle-lang-exts)
     46 (add-to-list 'org-babel-tangle-lang-exts '("jq" . "jq"))
     47 
     48 (defconst org-babel-header-args:jq
     49   '(
     50     (:in-file  . :any)
     51     (:cmd-line . :any)
     52     (:compact  . ((yes no)))
     53     )
     54   "Jq specific header arguments.")
     55 
     56 (defvar org-babel-default-header-args:jq '(
     57                                            (:results . "output")
     58                                            (:compact . "no")
     59                                            )
     60   "Default arguments for evaluating a jq source block.")
     61 
     62 (defun org-babel-jq-table-to-json (data)
     63   "Convert org table to JSON.
     64 
     65 First line specifies the keys."
     66   (let* ((header (car data))
     67          (data (cdr data)))
     68     (while (eq (car data) 'hline)
     69       (setq data (cdr data)))
     70     (json-encode
     71      (mapcar
     72       (lambda (row) (cl-mapcar 'cons header row))
     73       data))))
     74 
     75 (defun org-babel-jq-args (params)
     76   "Return an --arg argument for each PARAMS :var"
     77   (let ((vars (org-babel--get-vars params)))
     78     (and vars
     79          (mapconcat
     80           (lambda (var)
     81             (format "--arg %s %S" (car var) (cdr var)))
     82           vars
     83           " "))))
     84 
     85 (defun org-babel-execute:jq (body params)
     86   "Execute a block of jq code with org-babel.  This function is
     87 called by `org-babel-execute-src-block'"
     88   (message "executing jq source code block")
     89   (let* ((result-params (cdr (assq :result-params params)))
     90          (compact (equal "yes" (cdr (assq :compact params))))
     91          (cmd-line (cdr (assq :cmd-line params)))
     92          (vars (org-babel-jq-args params))
     93          (in-file (cdr (assq :in-file params)))
     94          (code-file (let ((file (org-babel-temp-file "jq-")))
     95                       (with-temp-file file
     96                         (insert body)
     97                         file)))
     98          (stdin (let ((stdin (cdr (assq :stdin params))))
     99                   (when stdin
    100                     (let ((tmp (org-babel-temp-file "jq-stdin-"))
    101                           (res (org-babel-ref-resolve stdin)))
    102                       (with-temp-file tmp
    103                         (insert
    104                          (cond
    105                           ((listp res) (org-babel-jq-table-to-json res))
    106                           (t res)))
    107                         tmp)))))
    108          (cmd (mapconcat #'identity
    109                          (remq nil
    110                                (list org-babel-jq-command
    111                                      (format "--from-file \"%s\"" code-file)
    112                                      (when compact "--compact-output")
    113                                      cmd-line
    114                                      vars
    115                                      in-file))
    116                          " ")))
    117     (org-babel-reassemble-table
    118      (let ((results
    119             (cond
    120              (stdin (with-temp-buffer
    121                       (call-process-shell-command cmd stdin (current-buffer))
    122                       (buffer-string)))
    123              (t (org-babel-eval cmd "")))))
    124        (when results
    125          (org-babel-result-cond result-params
    126            results
    127            (let ((data (json-read-from-string results)))
    128              ;; If we have an array we might have a table
    129              (if (and (vectorp data)
    130                       (> (length data) 0))
    131                  (cond
    132                   ;; If the first element is a vector then just "unpack"
    133                   ;; the vector of vectors
    134                   ((vectorp (aref data 0))
    135                    (mapcar (lambda (row) (append row nil)) data))
    136                   ;; If the first element is a list we will assume we
    137                   ;; have an array of objects, so generate the colnames
    138                   ;; accordingly
    139                   ((consp (aref data 0))
    140                    (let ((colnames (mapcar 'car (aref data 0))))
    141                      (unless (assq :colnames params)
    142                        (push `(:colnames . ,colnames) params))
    143                      (mapcar (lambda (row) (mapcar 'cdr row)) data)))
    144                   ;; For a vector of scalars just return it as an
    145                   ;; array, it will make a single-row table
    146                   (t (list (append data nil))))
    147                ;; If we have an object then just output it as string
    148                results)))))
    149      (org-babel-pick-name (cdr (assq :colname-names params))
    150                           (cdr (assq :colnames params)))
    151      (org-babel-pick-name (cdr (assq :rowname-names params))
    152                           (cdr (assq :rownames params))))))
    153 
    154 (provide 'ob-jq)
    155 ;;; ob-jq.el ends here