-
Notifications
You must be signed in to change notification settings - Fork 12
/
org-dashboard.el
260 lines (226 loc) · 9.38 KB
/
org-dashboard.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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
;;; org-dashboard.el --- Visually summarize progress in org files
;; Copyright (C) 2015-2017 Massimiliano Mirra
;; Author: Massimiliano Mirra <hyperstruct@gmail.com>
;; Version: 1.0
;; Maintainer: Massimiliano Mirra <hyperstruct@gmail.com>
;; Keywords: outlines, calendar
;; URL: http://github.com/bard/org-dashboard
;; Package-Requires: ((cl-lib "0.5"))
;; 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:
;; Org Dashboard provides a visual summary of progress on projects and
;; tasks.
;;
;; For example, if an org file (known by `org-dashboard-files') contains
;; the following:
;;
;; * Project: Better Health
;; :PROPERTIES:
;; :CATEGORY: health
;; :END:
;;
;; ** Milestones
;; *** [66%] run 10 km/week
;; **** TODO learn proper warmup
;; **** DONE look for a jogging partner
;; **** DONE run 10 minutes on monday
;;
;; * Project: Super Widget
;; :PROPERTIES:
;; :CATEGORY: widget
;; :END:
;;
;; ** Milestones
;; *** [1/6] release 0.1
;; **** DONE git import
;; **** TODO create github project
;; **** TODO add readme
;; **** TODO publish
;;
;; Then `M-x org-dashboard-display' generates the following report and
;; displays it in a new buffer:
;;
;; health run 10 km/week [|||||||||||||||||||||| ] 66%
;; widget 0.1 release [|||||| ] 18%
;;
;; A dynamic block form is also supported. Writing the following in an
;; org file and then running `org-dblock-update', or placing the
;; cursor on the first line of the block and then typing `C-c C-c',
;; will insert the same report shown above into the block:
;;
;; #+BEGIN: block-dashboard
;; #+END:
;; Configuration:
;;
;; You can customize the following variables:
;;
;; - `org-dashboard-files': list of files to search for progress entries; defaults to `org-agenda-files'
;; - `org-dashboard-show-category': whether to show or not the project category
;; - `org-dashboard-filter': a function that decides whether an entry should be displayed or not
;;
;; For example, to avoid displaying entries that are finished
;; (progress = 100), not started (progress = 0), or are tagged with
;; "archive", use the following:
;;
;; (defun my/org-dashboard-filter (entry)
;; (and (> (plist-get entry :progress-percent) 0)
;; (< (plist-get entry :progress-percent) 100)
;; (not (member "archive" (plist-get entry :tags)))))
;;
;; (setq org-dashboard-filter 'my/org-dashboard-filter)
;; Notes:
;;
;; Labels link back to the trees where they were found.
;;
;; The color of the progress bar is (naively, for now) chosen based on
;; the progress value, from dark red to bright green.
;;
;; If not set per-tree through a property or per-file through a
;; keyword, the category defaults to the file name without extension.
;; To set category on a per-file basis, you can add the following at
;; the bottom of the org file:
;;
;; #+CATEGORY: xyz
;; Related work:
;;
;; This module was inspired by Zach Peter's [A Dashboard for your
;; Life](http://thehelpfulhacker.net/2014/07/19/a-dashboard-for-your-life-a-minimal-goal-tracker-using-org-mode-go-and-git/).
;; Contributions:
;;
;; - one feature or fix per pull request
;; - provide an example of the problem it addresses
;; - please adhere to the existing code style
;;; Code:
(require 'org)
(require 'cl-lib)
(require 'subr-x)
(defgroup org-dashboard nil
"Options concerning org dashboard."
:tag "Org Dashboard"
:group 'org)
(defcustom org-dashboard-files
org-agenda-files
"Files to search for progress items."
:type '(repeat :tag "List of files and directories" file)
:group 'org-dashboard)
(defcustom org-dashboard-show-category
t
"Whether to display categories in a progress report.
Note that, if not set with per-file or per-tree properties,
category defaults to the org file name."
:group 'org-dashboard
:type 'boolean)
(defcustom org-dashboard-filter nil
"Function to use to filter progress entries."
:group 'org-dashboard
:type 'function)
;;;###autoload
(defun org-dashboard-display ()
(interactive)
(with-current-buffer (get-buffer-create "*Org Dashboard*")
(setq buffer-read-only nil)
(erase-buffer)
(org-mode)
(save-excursion
(org-dashboard--insert-progress-summary
(org-dashboard--collect-progress)))
(setq buffer-read-only t)
(display-buffer (current-buffer))))
;;;###autoload
(defun org-dblock-write:block-dashboard (params)
"Generate a progress report inside an org dynamic block.
Progress information is retrieved by searching files in
`org-dashboard-files' for headings containing a \"progress cookie\",
e.g.:
** [50%] release v0.1
*** TODO publish on github
*** DONE import in git
See Info node `(org) Breaking down tasks'."
(org-dashboard--insert-progress-summary
(org-dashboard--collect-progress)))
(defvar org-dashboard--cookie-re
"\\[[0-9]+\\(%\\|/[0-9]+\\)\\]")
(defun org-dashboard--collect-progress ()
(cl-remove-if-not
org-dashboard-filter
(cl-loop for file in org-dashboard-files
append (with-current-buffer (find-file-noselect file)
(org-with-wide-buffer
(org-dashboard--collect-progress-current-buffer))))))
(defun org-dashboard--insert-progress-summary (progress-summary)
(cl-labels
((make-category-label (category)
(truncate-string-to-width category 10 0 ?\s "…"))
(make-goal-label (goal)
(truncate-string-to-width goal 25 0 nil "…"))
(make-progress-bar (progress-percent)
(let ((color (org-dashboard--progress-color progress-percent)))
(concat (propertize
(make-string (/ progress-percent 4) ?|)
'font-lock-face (list :foreground color))
(make-string (- (/ 100 4) (/ progress-percent 4)) ?\s))))
(make-link (target label)
(format "[[%s][%s]]" target label)))
(insert "\n")
(cl-loop for entry in progress-summary
do (cl-destructuring-bind
(&key category heading id progress-percent filename tags)
entry
(let* ((category-label (make-category-label category))
(goal-label (make-goal-label heading))
(goal-link (make-link (if id (concat "id:" id)
(concat filename "::*" heading))
goal-label))
(goal-label-padding (make-string (- 25 (string-width goal-label)) ?\s))
(progress-bar (make-progress-bar progress-percent))
(percent-indicator (format "%3d%%" progress-percent)))
(insert (format "%s %s%s [%s] %s\n"
(if org-dashboard-show-category
category-label
"")
goal-label-padding
goal-link
progress-bar
percent-indicator)))))))
(defun org-dashboard--collect-progress-current-buffer ()
(save-excursion
(goto-char (point-min))
(org-refresh-category-properties)
(cl-loop while (re-search-forward org-dashboard--cookie-re nil t)
if (org-at-heading-p)
collect (list :category (substring-no-properties (org-get-category))
:heading (org-dashboard--get-heading-text)
:id (org-id-get)
:progress-percent (org-dashboard--get-heading-progress)
:filename (buffer-file-name)
:tags (org-get-tags)))))
(defun org-dashboard--get-heading-text ()
(string-trim
(replace-regexp-in-string org-dashboard--cookie-re
""
(nth 4 (org-heading-components)))))
(defun org-dashboard--get-heading-progress ()
(let* ((heading-with-cookie (nth 4 (org-heading-components)))
(_ (string-match org-dashboard--cookie-re (nth 4 (org-heading-components))))
(cookie (substring heading-with-cookie 0 (match-end 0))))
(cond ((string-match "\\([0-9]+\\)%" cookie)
(string-to-number (match-string 1 cookie)))
((string-match "\\([0-9]+\\)/\\([0-9]+\\)" cookie)
(/ (* 100 (string-to-number (match-string 1 cookie)))
(string-to-number (match-string 2 cookie)))))))
(defun org-dashboard--progress-color (percent)
(cond ((< percent 33) "red")
((< percent 66) "dark green")
((< percent 100) "forest green")
(t "green")))
(provide 'org-dashboard)
;;; org-dashboard.el ends here