-
Notifications
You must be signed in to change notification settings - Fork 2
/
org-tagged.el
205 lines (181 loc) · 7.36 KB
/
org-tagged.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
;;; org-tagged.el --- Dynamic block for tagged org-mode todos -*- lexical-binding: t -*-
;; Copyright (C) 2022 Christian Köstlin
;; This file is NOT part of GNU Emacs.
;; Author: Christian Köstlin <[email protected]>
;; Keywords: org-mode, org, gtd, tools
;; Package-Requires: ((s "1.13.0") (dash "2.19.1") (emacs "28.1") (org "9.5.2"))
;; Version: 0.0.6
;; Homepage: http://github.com/gizmomogwai/org-tagged
;; SPDX-License-Identifier: MIT
;;; Commentary:
;; To create a tagged table for an org file, simply put the dynamic block
;; `
;; #+BEGIN: tagged :columns "%10tag1(Tag1)|tag2" :match "kanban"
;; #+END:
;; '
;; somewhere and run `C-c C-c' on it.
;;; Code:
(require 's)
(require 'dash)
(require 'org)
(require 'org-table)
(require 'wid-edit)
(defun org-tagged--get-data-from-heading (entry)
"Extract the needed information from an ENTRY.
Return a list with
- the heading
- the tags as list of strings."
(list
(nth 4 entry)
org-scanner-tags))
(defun org-tagged--row-for (heading item-tags columns truncation-string)
"Create a row for a HEADING with ITEM-TAGS.
The table is specified by COLUMNS. Headings are truncated if the
format specifies it by TRUNCATION-STRING."
(let* ((result (format "|%s|" (s-join "|"
(--map
(if (-elem-index (nth 1 it) item-tags)
(format "[[*%s][%s]]" heading (s-truncate (nth 0 it) heading truncation-string))
"")
columns)))))
(if (eq (length result) (1+ (length columns))) nil result)))
(defun org-tagged-version ()
"Print org-tagge version."
(interactive)
(message "org-tagged 0.0.6"))
(defun org-tagged--parse-column (column-description)
"Parse a column from a COLUMN-DESCRIPTION.
Each column description consists of:
- maximum length (defaults to 1000)
- tag to select the elements that go into the column
- title of the column (defaults to the tag)"
(string-match
(rx
string-start
(optional (and "%" (group (one-or-more digit))))
(group (minimal-match (1+ anything)))
(optional (and "(" (group (+? anything)) ")"))
string-end)
column-description)
(list
(string-to-number (or (match-string 1 column-description) "1000"))
(match-string 2 column-description)
(or (match-string 3 column-description) (match-string 2 column-description))))
(defun org-tagged--get-columns (columns-description)
"Parse the column descriptions out of COLUMNS-DESCRIPTION.
The columns are separated by `|'."
(--map (org-tagged--parse-column it) (s-split "|" columns-description)))
(defun org-tagged--calculate-preview (columns match truncation-string)
"Calculate the org-tagged header.
Its calculated from COLUMNS, MATCH and TRUNCATION-STRING."
(s-join " " (delq nil
(list "#+BEGIN: tagged"
(format ":columns \"%s\"" columns)
(if match (format ":match \"%s\"" match) nil)
(format ":truncation-string \"%s\"" truncation-string)))))
(defun org-tagged--update-preview (preview columns match truncation-string)
"Update the PREVIEW widget.
The header is calculated from COLUMNS, MATCH and TRUNCATION-STRING."
(widget-value-set preview
(org-tagged--calculate-preview
columns
match
truncation-string)))
(defun org-tagged--show-configure-buffer (buffer beginning parameters)
"Create the configuration form for BUFFER.
BEGINNING the position there and
PARAMETERS the org-tagged parameters."
(switch-to-buffer "*org-tagged-configure*")
(let* (
(inhibit-read-only t)
(preview nil)
(columns (plist-get parameters :columns))
(match (plist-get parameters :match))
(truncation-string (or (plist-get parameters :truncation-string) "…")))
(erase-buffer)
(remove-overlays)
(widget-insert (propertize "Columns: " 'face 'font-lock-keyword-face))
(widget-create 'editable-field
:value (format "%s" (or columns ""))
:size 40
:notify (lambda (widget &rest _ignore)
(setq columns (widget-value widget))
(org-tagged--update-preview
preview columns match truncation-string)))
(widget-insert "\n")
(widget-insert (propertize " select columns in the format [%LENGTH]TAG[(TITLE)]|..." 'face 'font-lock-doc-face))
(widget-insert "\n\n")
(widget-insert (propertize "Match: " 'face 'font-lock-keyword-face))
(widget-create 'editable-field
:value (format "%s" (or match ""))
:size 40
:notify (lambda (widget &rest _ignore)
(setq match (widget-value widget))
(org-tagged--update-preview preview
columns match truncation-string)))
(widget-insert "\n")
(widget-insert (propertize " match to tags e.g. urgent|important" 'face 'font-lock-doc-face))
(widget-insert "\n\n")
(widget-insert (propertize "Truncation string: " 'face 'font-lock-keyword-face))
(widget-create 'editable-field
:value (format "%s" truncation-string)
:size 10
:notify (lambda (widget &rest _ignore)
(setq truncation-string (widget-value widget))
(org-tagged--update-preview
preview columns match truncation-string)))
(widget-insert "\n")
(widget-insert (propertize " string truncation indicator" 'face 'font-lock-doc-face))
(widget-insert "\n\n")
(widget-insert (propertize "Result: " 'face 'font-lock-keyword-face))
(setq preview
(widget-create 'const))
(widget-create 'push-button
:notify (lambda(_widget &rest _ignore)
(with-current-buffer buffer
(goto-char beginning)
(kill-line)
(insert (org-tagged--calculate-preview columns match truncation-string)))
(kill-buffer)
(org-ctrl-c-ctrl-c))
(propertize "Apply" 'face 'font-lock-comment-face))
(widget-insert " ")
(widget-create 'push-button
:notify (lambda (_widget &rest _ignore)
(kill-buffer))
(propertize "Cancel" 'face 'font-lock-string-face))
(org-tagged--update-preview preview columns match truncation-string)
(use-local-map widget-keymap)
(widget-setup)))
;;;###autoload
(defun org-dblock-write:tagged (params)
"Create a tagged dynamic block.
PARAMS must contain: `:tags`."
(insert
(let*
(
(truncation-string (or (plist-get params :truncation-string) "…"))
(columns
(org-tagged--get-columns (plist-get params :columns)))
(todos
(org-map-entries
(lambda () (org-tagged--get-data-from-heading (org-heading-components))) (plist-get params :match)))
(table
(s-join "\n" (remove nil (--map (org-tagged--row-for (nth 0 it) (nth 1 it) columns truncation-string) todos)))))
(format "|%s|\n|--|\n%s" (s-join "|" (--map (nth 2 it) columns)) table)))
(org-table-align))
(defun org-tagged-initialize ()
"Create an org-tagged dynamic block at the point."
(interactive)
(save-excursion
(insert "#+BEGIN: tagged :columns \"%25tag1(Title)|tag2\" :match \"kanban\" :truncation-string \" >\"\n#+END:\n"))
(org-ctrl-c-ctrl-c))
(defun org-tagged-configure-block ()
"Configure the current org-tagged dynamic block."
(interactive)
(let* (
(beginning (org-beginning-of-dblock))
(parameters (org-prepare-dblock)))
(org-tagged--show-configure-buffer (current-buffer) beginning parameters)))
(provide 'org-tagged)
;;; org-tagged.el ends here