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