├── .rice-lock
└── default
│ ├── archive.lock
│ ├── flake.nix
│ └── flake.lock
├── .gitignore
├── .gitattributes
├── test
├── no-datetree.org
├── mixed.org
├── month.org
├── month-and-week.org
├── time.org
└── custom-tree.org
├── screenshots
├── calendar.png
└── org-reverse-datetree-1.png
├── flake.nix
├── .github
├── workflows
│ └── check-emacs-lisp.yml
└── renovate.json
├── justfile
├── org-reverse-datetree-test.el
├── README.org
├── LICENSE
└── org-reverse-datetree.el
/.rice-lock/default/archive.lock:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled files
2 | *.elc
3 |
4 | result
5 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Copied from the template
2 | justfile linguist-vendored
3 |
--------------------------------------------------------------------------------
/test/no-datetree.org:
--------------------------------------------------------------------------------
1 | #+title: Org File without a Date Tree
2 | * Sample
3 |
--------------------------------------------------------------------------------
/screenshots/calendar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akirak/org-reverse-datetree/HEAD/screenshots/calendar.png
--------------------------------------------------------------------------------
/screenshots/org-reverse-datetree-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akirak/org-reverse-datetree/HEAD/screenshots/org-reverse-datetree-1.png
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | outputs = {...}: {
3 | elisp-rice = {
4 | packages = [
5 | "org-reverse-datetree"
6 | ];
7 | tests = {
8 | buttercup.enable = true;
9 | };
10 | };
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/test/mixed.org:
--------------------------------------------------------------------------------
1 | #+REVERSE_DATETREE_USE_WEEK_TREE: nil
2 | #+REVERSE_DATETREE_DATE_FORMAT: %Y-%m-%d %A
3 | #+REVERSE_DATETREE_MONTH_FORMAT: %Y-%m
4 | #+REVERSE_DATETREE_YEAR_FORMAT: %Y
5 | * 2020
6 | ** 2020-12
7 | *** 2020-12-31 Thursday
8 | **** D
9 | **** E
10 | * Non-datetree
11 | ** This is not a datetree
12 | *** This is not a date
13 | **** Child
14 |
--------------------------------------------------------------------------------
/test/month.org:
--------------------------------------------------------------------------------
1 | #+REVERSE_DATETREE_USE_WEEK_TREE: nil
2 | #+REVERSE_DATETREE_DATE_FORMAT: %Y-%m-%d %A
3 | #+REVERSE_DATETREE_MONTH_FORMAT: %Y-%m
4 | #+REVERSE_DATETREE_YEAR_FORMAT: %Y
5 | * 2021
6 | ** 2021-04
7 | *** 2021-04-02 Friday
8 | **** A
9 | *** 2021-04-01 Thursday
10 | **** B
11 | ***** B.1
12 | ** 2021-01
13 | *** 2021-01-01 Friday
14 | **** C
15 | * 2020
16 | ** 2020-12
17 | *** 2020-12-31 Thursday
18 | **** D
19 | **** E
20 |
--------------------------------------------------------------------------------
/.rice-lock/default/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description =
3 | "THIS IS AN AUTO-GENERATED FILE. PLEASE DON'T EDIT IT MANUALLY.";
4 | inputs = {
5 | buttercup = {
6 | flake = false;
7 | owner = "jorgenschaefer";
8 | repo = "emacs-buttercup";
9 | type = "github";
10 | };
11 | dash = {
12 | flake = false;
13 | owner = "magnars";
14 | repo = "dash.el";
15 | type = "github";
16 | };
17 | };
18 | outputs = { ... }: { };
19 | }
20 |
--------------------------------------------------------------------------------
/test/month-and-week.org:
--------------------------------------------------------------------------------
1 | #+REVERSE_DATETREE_USE_WEEK_TREE: month-and-week
2 | #+REVERSE_DATETREE_DATE_FORMAT: %Y-%m-%d %A
3 | #+REVERSE_DATETREE_WEEK_FORMAT: %Y W%W
4 | #+REVERSE_DATETREE_MONTH_FORMAT: %Y-%m
5 | #+REVERSE_DATETREE_YEAR_FORMAT: %Y
6 | * 2021
7 | ** 2021-02
8 | *** 2021 W05
9 | **** 2021-02-01 Monday
10 | ***** X
11 | ** 2021-01
12 | *** 2021 W00
13 | **** 2021-01-01 Friday
14 | ***** Y
15 | * 2020
16 | ** 2020-08
17 | *** 2020 W30
18 | **** 2020-08-01 Saturday
19 | ***** Z
20 |
--------------------------------------------------------------------------------
/.github/workflows/check-emacs-lisp.yml:
--------------------------------------------------------------------------------
1 | name: Check Emacs Lisp
2 | on:
3 | pull_request:
4 | paths-ignore:
5 | - 'README.*'
6 | push:
7 | branches:
8 | - master
9 | paths-ignore:
10 | - 'README.*'
11 | workflow_dispatch:
12 | jobs:
13 | compile-and-test:
14 | uses: emacs-twist/elisp-workflows/.github/workflows/compile-and-test.yml@master
15 | with:
16 | systems: github:nix-systems/x86_64-linux
17 | melpazoid:
18 | uses: emacs-twist/elisp-workflows/.github/workflows/melpazoid.yml@master
19 |
--------------------------------------------------------------------------------
/test/time.org:
--------------------------------------------------------------------------------
1 | * Parent
2 | CLOSED: [2022-02-26 Sat 22:12]
3 | :PROPERTIES:
4 | :CREATED_TIME: [2022-01-31 Mon 15:40]
5 | :END:
6 | :LOGBOOK:
7 | CLOCK: [2022-01-31 Mon 15:40]--[2022-01-31 Mon 16:17] => 0:37
8 | :END:
9 | ** Child
10 | :LOGBOOK:
11 | CLOCK: [2022-01-20 Thu 01:54]--[2022-01-20 Thu 02:05] => 0:11
12 | :END:
13 | * [2022-03-10 Thu]
14 | :PROPERTIES:
15 | :CUSTOM_ID: clock-in-heading-1
16 | :END:
17 | <2022-02-24 Thu 15:33>
18 | ** <2022-03-01 Tue 12:24>
19 | :PROPERTIES:
20 | :CUSTOM_ID: clock-in-heading-2
21 | :END:
22 | [2022-03-10 Thu]
23 |
--------------------------------------------------------------------------------
/test/custom-tree.org:
--------------------------------------------------------------------------------
1 | #+title: Test file 1
2 | * Test
3 | #+begin_src emacs-lisp
4 | (setq-local org-reverse-datetree-level-formats
5 | '("%Y"
6 | (lambda (time) (format-time-string "%Y-%m %B" (org-reverse-datetree-monday time)))
7 | "%Y-%m-%d %A"))
8 | #+end_src
9 | * 2021
10 | ** 2021-01 January
11 | *** 2021-01-04 Monday
12 | * 2020
13 | ** 2020-12 December
14 | *** 2020-12-10 Thursday
15 | **** Bye
16 | My life has ended.
17 | ** 2020-11 November
18 | *** 2020-11-30 Monday
19 | *** 2020-11-28 Saturday
20 | **** Hello
21 | *** 2020-11-24 Tuesday
22 | *** 2020-11-18 Wednesday
23 | * 2019
24 | ** 2019-12 December
25 | *** 2019-12-12 Thursday
26 |
--------------------------------------------------------------------------------
/.rice-lock/default/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "buttercup": {
4 | "flake": false,
5 | "locked": {
6 | "lastModified": 1754006498,
7 | "narHash": "sha256-uDNLdTJfNYVFJq6oFH/QqLWEvomrtID60QtFXvLUyCY=",
8 | "owner": "jorgenschaefer",
9 | "repo": "emacs-buttercup",
10 | "rev": "cc5a2ab7c7f18aaaf525fac61fe59bae5ad018dd",
11 | "type": "github"
12 | },
13 | "original": {
14 | "owner": "jorgenschaefer",
15 | "repo": "emacs-buttercup",
16 | "type": "github"
17 | }
18 | },
19 | "dash": {
20 | "flake": false,
21 | "locked": {
22 | "lastModified": 1760611661,
23 | "narHash": "sha256-DjUHlCKt1mX72ahkxZxHywyw5pcS1JSFB2Rr+gnIGlQ=",
24 | "owner": "magnars",
25 | "repo": "dash.el",
26 | "rev": "fb443e7a6e660ba849cafcd01021d9aac3ac6764",
27 | "type": "github"
28 | },
29 | "original": {
30 | "owner": "magnars",
31 | "repo": "dash.el",
32 | "type": "github"
33 | }
34 | },
35 | "root": {
36 | "inputs": {
37 | "buttercup": "buttercup",
38 | "dash": "dash"
39 | }
40 | }
41 | },
42 | "root": "root",
43 | "version": 7
44 | }
45 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:recommended",
5 | "helpers:pinGitHubActionDigests"
6 | ],
7 | "labels": [
8 | "automation",
9 | "dependencies"
10 | ],
11 | "packageRules": [
12 | {
13 | "matchUpdateTypes": [
14 | "lockFileMaintenance"
15 | ],
16 | "groupName": "Maintenance",
17 | "extends": [
18 | "schedule:earlyMondays"
19 | ],
20 | "automerge": true
21 | },
22 | {
23 | "matchManagers": [
24 | "github-actions"
25 | ],
26 | "groupName": "actions",
27 | "separateMajorMinor": false,
28 | "automerge": true
29 | },
30 | {
31 | "matchUpdateTypes": [
32 | "patch",
33 | "pin",
34 | "digest",
35 | "minor"
36 | ],
37 | "groupName": "Patches",
38 | "schedule": [
39 | "before 4am on the first day of the month"
40 | ],
41 | "automerge": true
42 | }
43 | ],
44 | "automergeSchedule": [
45 | "before 7pm"
46 | ],
47 | "lockFileMaintenance": {
48 | "enabled": true
49 | },
50 | "github-actions": {
51 | "enabled": true
52 | },
53 | "nix": {
54 | "enabled": true
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/justfile:
--------------------------------------------------------------------------------
1 | # Update this if you have forked rice-config rice-config := rice-config :=
2 | rice-config := "github:emacs-twist/rice-config"
3 |
4 | # Specify a flake reference to a repository and branch where the package recipe
5 | # is defined.
6 | melpa := "github:melpa/melpa/master"
7 |
8 | # Relative path to the lock directory
9 | lock-dir := ".rice-lock/default"
10 |
11 | # This is only to avoid repetition, and you usually don't edit this.
12 | common-options-without-lock := "--override-input rice-src \"path:$PWD\" --override-input melpa " + quote(melpa)
13 |
14 | common-options-with-lock := common-options-without-lock + " --override-input rice-lock \"path:$PWD/" + lock-dir + "\""
15 |
16 | # The name of an Emacs package from nix-emacs-ci
17 | emacs := "emacs-release-snapshot"
18 |
19 | # Name of the package under test
20 | package := "org-reverse-datetree"
21 |
22 | # Don't edit this
23 | arch := shell('nix eval --expr builtins.currentSystem --impure --raw')
24 |
25 | # Show the flake
26 | show *OPTIONS:
27 | nix flake show {{ rice-config }} {{ OPTIONS }} {{ common-options-with-lock }}
28 |
29 | # Evaluate an attribute on the flake, e.g. just eval melpaRecipes.
30 | eval ATTR *OPTIONS:
31 | nix eval {{rice-config}}\#{{ATTR}} {{OPTIONS}} {{ common-options-with-lock }}
32 |
33 | # Generate a lock directory.
34 | lock *OPTIONS:
35 | mkdir -p "$(dirname {{ lock-dir }})"
36 | nix run "{{ rice-config }}#{{ emacs }}-with-packages.generateLockDir" {{ common-options-without-lock }} --impure -- {{ OPTIONS }} {{ lock-dir }}
37 |
38 | # Enter a shell for byte-compiling individual source files
39 | shell-compile:
40 | nix develop {{ rice-config }}\#{{ emacs }}-for-{{ package }} {{ common-options-with-lock }}
41 |
42 | # Re-run byte-compile every time a file is modified
43 | watch-compile:
44 | nix develop {{ rice-config }}\#{{ emacs }}-for-{{ package }} {{ common-options-with-lock }} -c bash -c 'echo >&2 Watching *.el; ls *.el | entr -p elisp-byte-compile /_'
45 |
46 | # Byte-compile the package
47 | check-compile:
48 | nix build {{ rice-config }}\#checks.{{ arch }}.{{ package }}-compile-{{ emacs }} {{ common-options-with-lock }} --print-build-logs
49 |
50 | # Enter a shell for running tests
51 | shell-emacs *OPTIONS:
52 | nix shell {{ rice-config }}\#{{ emacs }}-with-packages {{ common-options-with-lock }} {{ OPTIONS }}
53 |
54 | test-buttercup *OPTIONS:
55 | nix run {{ rice-config }}\#test-buttercup-with-{{ emacs }} {{ common-options-with-lock }} {{ OPTIONS }}
56 |
--------------------------------------------------------------------------------
/org-reverse-datetree-test.el:
--------------------------------------------------------------------------------
1 | ;;; -*- lexical-binding: t; -*-
2 |
3 | (require 'buttercup)
4 | (require 'org-reverse-datetree)
5 |
6 | (defmacro org-reverse-datetree-test-with-file (file &rest progn)
7 | (declare (indent 1))
8 | `(with-temp-buffer
9 | (insert-file-contents ,file)
10 | (setq buffer-file-name ,file)
11 | (set-buffer-modified-p nil)
12 | (org-mode)
13 | ,@progn))
14 |
15 | (defun org-reverse-datetree-test--collect-headings (level)
16 | (let (result)
17 | (goto-char (point-min))
18 | (while (re-search-forward (rx-to-string `(and bol
19 | ,(make-string level ?*)
20 | (+ space)))
21 | nil t)
22 | (push (buffer-substring-no-properties (point) (line-end-position))
23 | result))
24 | (nreverse result)))
25 |
26 | (describe "org-reverse-datetree-cleanup-empty-dates"
27 | (it "On custom-tree.org"
28 | (let ((headings (with-temp-buffer
29 | (insert-file-contents "test/custom-tree.org")
30 | (setq buffer-file-name "test/custom-tree.org")
31 | (org-mode)
32 | (setq-local org-reverse-datetree-level-formats
33 | '("%Y"
34 | (lambda (time)
35 | (format-time-string
36 | "%Y-%m %B"
37 | (org-reverse-datetree-monday time)))
38 | "%Y-%m-%d %A"))
39 | (goto-char (point-min))
40 | (org-reverse-datetree-cleanup-empty-dates :noconfirm t
41 | :ancestors t)
42 | (set-buffer-modified-p nil)
43 | (cl-loop for level in (number-sequence 1 4)
44 | collect (cons level
45 | (org-reverse-datetree-test--collect-headings level))))))
46 | (expect (alist-get 4 headings)
47 | :to-equal '("Bye"
48 | "Hello"))
49 | (expect (alist-get 3 headings)
50 | :to-equal '("2020-12-10 Thursday"
51 | "2020-11-28 Saturday"))
52 | (expect (alist-get 2 headings)
53 | :to-equal '("2020-12 December" "2020-11 November"))
54 | (expect (alist-get 1 headings)
55 | :to-equal '("Test" "2020")))))
56 |
57 | (describe "org-reverse-datetree--entry-time-2"
58 | (describe "Default"
59 | (let ((org-use-effective-time nil)
60 | (results (with-temp-buffer
61 | (insert-file-contents "test/time.org")
62 | ;; (setq buffer-file-name "test/time.org")
63 | (org-mode)
64 | (goto-char (point-min))
65 | (let ((org-reverse-datetree-entry-time
66 | (eval (car (get 'org-reverse-datetree-entry-time
67 | 'standard-value)))))
68 | (list (org-reverse-datetree--entry-time-2)
69 | (progn
70 | (re-search-forward (rx bol "** Child"))
71 | (org-reverse-datetree--entry-time-2)))))))
72 | (it "takes the closed property if available"
73 | (expect (nth 0 results)
74 | :to-equal
75 | (org-reverse-datetree--encode-time
76 | (list 0 12 22 26 2 2022 nil nil nil))))
77 | (it "takes the latest clock finish"
78 | (expect (nth 1 results)
79 | :to-equal
80 | (org-reverse-datetree--encode-time
81 | (list 0 5 2 20 1 2022 nil nil nil))))))
82 |
83 | (describe "org-use-effective-time is t"
84 | (it "Consider org-extend-today-until"
85 | (expect (let ((org-use-effective-time t)
86 | (org-extend-today-until 5))
87 | (with-temp-buffer
88 | (insert-file-contents "test/time.org")
89 | ;; (setq buffer-file-name "test/time.org")
90 | (org-mode)
91 | (goto-char (point-min))
92 | (let ((org-reverse-datetree-entry-time
93 | (eval (car (get 'org-reverse-datetree-entry-time
94 | 'standard-value)))))
95 | (re-search-forward (rx bol "** Child"))
96 | (org-reverse-datetree--entry-time-2))))
97 | :to-equal
98 | (org-reverse-datetree--encode-time
99 | (list 0 59 23 19 1 2022 nil nil nil)))))
100 |
101 | (describe "With an argument"
102 | (let ((result (with-temp-buffer
103 | (insert-file-contents "test/time.org")
104 | ;; (setq buffer-file-name "test/time.org")
105 | (org-mode)
106 | (goto-char (point-min))
107 | (org-reverse-datetree--entry-time-2 '((property "CREATED_TIME"))))))
108 | (it "takes the creation time if available"
109 | (expect result
110 | :to-equal
111 | (org-reverse-datetree--encode-time
112 | (list 0 40 15 31 1 2022 nil nil nil)))))
113 | (let ((result (with-temp-buffer
114 | (insert-file-contents "test/time.org")
115 | ;; (setq buffer-file-name "test/time.org")
116 | (org-mode)
117 | (goto-char (point-min))
118 | (org-reverse-datetree--entry-time-2 '((clock earliest))))))
119 | (it "takes the earliest clock if available"
120 | (expect result
121 | :to-equal
122 | (org-reverse-datetree--encode-time
123 | (list 0 40 15 31 1 2022 nil nil nil)))))
124 | (pcase-let ((`(,result-inactive ,result-active)
125 | (with-temp-buffer
126 | (insert-file-contents "test/time.org")
127 | ;; (setq buffer-file-name "test/time.org")
128 | (org-mode)
129 | (goto-char (org-find-property "CUSTOM_ID" "clock-in-heading-1"))
130 | (list (org-reverse-datetree--entry-time-2 '((match :type inactive)))
131 | (org-reverse-datetree--entry-time-2 '((match :type active)))))))
132 | (it "matches the first inactive clock"
133 | (expect result-inactive
134 | :to-equal
135 | (org-reverse-datetree--encode-time
136 | (list 0 0 0 10 3 2022 nil nil nil))))
137 | (it "matches the first active clock"
138 | (expect result-active
139 | :to-equal
140 | (org-reverse-datetree--encode-time
141 | (list 0 33 15 24 2 2022 nil nil nil)))))
142 | (pcase-let ((`(,result-any ,result-default)
143 | (with-temp-buffer
144 | (insert-file-contents "test/time.org")
145 | ;; (setq buffer-file-name "test/time.org")
146 | (org-mode)
147 | (goto-char (org-find-property "CUSTOM_ID" "clock-in-heading-2"))
148 | (list (org-reverse-datetree--entry-time-2 '((match :type any)))
149 | (org-reverse-datetree--entry-time-2 '((match :type nil)))))))
150 | (it "matches the first any clock"
151 | (expect result-any
152 | :to-equal
153 | (org-reverse-datetree--encode-time
154 | (list 0 24 12 1 3 2022 nil nil nil))))
155 | (it "matches the first inactive clock"
156 | (expect result-default
157 | :to-equal
158 | (org-reverse-datetree--encode-time
159 | (list 0 0 0 10 3 2022 nil nil nil)))))))
160 |
161 | (describe "org-reverse-datetree-map-entries"
162 |
163 | (describe "With DATE-REGEXP"
164 | (it "calls FUNC with the matched string"
165 |
166 | (expect (with-temp-buffer
167 | (insert-file-contents "test/month.org")
168 | (setq buffer-file-name "test/month.org")
169 | (set-buffer-modified-p nil)
170 | (org-mode)
171 | (goto-char (point-min))
172 | (org-reverse-datetree-map-entries
173 | (lambda (date)
174 | (list date (org-get-heading)))
175 | :date-regexp "2021-[[:digit:]]\\{2\\}-[[:digit:]]\\{2\\}"))
176 | :to-equal '(("2021-04-02" "A")
177 | ("2021-04-01" "B")
178 | ("2021-01-01" "C")))))
179 |
180 | (describe "Without DATE-REGEXP"
181 | (it "calls FUNC with the matched string"
182 |
183 | (expect (with-temp-buffer
184 | (insert-file-contents "test/month.org")
185 | (setq buffer-file-name "test/month.org")
186 | (set-buffer-modified-p nil)
187 | (org-mode)
188 | (goto-char (point-min))
189 | (org-reverse-datetree-map-entries
190 | (lambda (date)
191 | (list date (org-get-heading)))))
192 | :to-equal '(("2021-04-02 Friday" "A")
193 | ("2021-04-01 Thursday" "B")
194 | ("2021-01-01 Friday" "C")
195 | ("2020-12-31 Thursday" "D")
196 | ("2020-12-31 Thursday" "E")))
197 |
198 | (expect (with-temp-buffer
199 | (insert-file-contents "test/month-and-week.org")
200 | (setq buffer-file-name "test/month-and-week.org")
201 | (set-buffer-modified-p nil)
202 | (org-mode)
203 | (goto-char (point-min))
204 | (org-reverse-datetree-map-entries
205 | (lambda (date)
206 | (list date (org-get-heading)))))
207 | :to-equal '(("2021-02-01 Monday" "X")
208 | ("2021-01-01 Friday" "Y")
209 | ("2020-08-01 Saturday" "Z"))))))
210 |
211 | (describe "org-reverse-datetree-dates"
212 |
213 | (it "returns dates, without duplicates"
214 | (expect (org-reverse-datetree-test-with-file "test/month.org"
215 | (goto-char (point-min))
216 | (org-reverse-datetree-dates))
217 | :to-equal
218 | (mapcar (pcase-lambda (`(,year ,month ,day))
219 | (org-reverse-datetree--encode-time
220 | (append (list 0 0 0 day month year)
221 | (seq-drop (decode-time (current-time)) 6))))
222 | '((2020 12 31)
223 | (2021 1 1)
224 | (2021 4 1)
225 | (2021 4 2)))))
226 |
227 | (it ":decoded t"
228 | (expect (org-reverse-datetree-test-with-file "test/month.org"
229 | (goto-char (point-min))
230 | (mapcar (lambda (decoded-time)
231 | (seq-take decoded-time 6))
232 | (org-reverse-datetree-dates :decoded t)))
233 | :to-equal
234 | (mapcar (lambda (decoded-time)
235 | (seq-take decoded-time 6))
236 | (list (parse-time-string "2020-12-31")
237 | (parse-time-string "2021-01-01")
238 | (parse-time-string "2021-04-01")
239 | (parse-time-string "2021-04-02")))))
240 |
241 | (it "skip non-datetree"
242 | (expect (org-reverse-datetree-test-with-file "test/mixed.org"
243 | (goto-char (point-min))
244 | (org-reverse-datetree-dates))
245 | :to-equal
246 | (mapcar (pcase-lambda (`(,year ,month ,day))
247 | (org-reverse-datetree--encode-time
248 | (append (list 0 0 0 day month year)
249 | (seq-drop (decode-time (current-time)) 6))))
250 | '((2020 12 31))))))
251 |
252 | (describe "org-reverse-datetree-guess-date"
253 |
254 | (it "returns nil when the entry is outside of the datetree"
255 | (expect (org-reverse-datetree-test-with-file "test/month.org"
256 | (goto-char (point-min))
257 | (org-reverse-datetree-guess-date))
258 | :to-be nil)
259 |
260 | (expect (org-reverse-datetree-test-with-file "test/mixed.org"
261 | (goto-char (point-max))
262 | (org-reverse-datetree-guess-date))
263 | :to-be nil))
264 |
265 | (it "returns nil when the file has no datetree"
266 | (expect (org-reverse-datetree-test-with-file "test/no-datetree.org"
267 | (goto-char (point-min))
268 | (save-match-data
269 | (re-search-forward org-heading-regexp))
270 | (org-reverse-datetree-guess-date))
271 | :to-be nil))
272 |
273 | (it "returns nil when the entry is not under a date, e.g. on a year or month"
274 | (expect (org-reverse-datetree-test-with-file "test/month.org"
275 | (goto-char (point-min))
276 | (search-forward "** 2021-04")
277 | (org-reverse-datetree-guess-date))
278 | :to-be nil))
279 |
280 | (it "returns the date when the entry is on a date"
281 | (expect (org-reverse-datetree-test-with-file "test/month.org"
282 | (goto-char (point-min))
283 | (search-forward "*** 2021-04-02 Friday")
284 | (org-reverse-datetree-guess-date))
285 | :to-equal (org-reverse-datetree--encode-time
286 | (append '(0 0 0 2 4 2021)
287 | (seq-drop (decode-time (current-time)) 6)))))
288 |
289 | (it "returns the date when the entry is under a date"
290 | (expect (org-reverse-datetree-test-with-file "test/month.org"
291 | (goto-char (point-min))
292 | (search-forward "**** B")
293 | (org-reverse-datetree-guess-date))
294 | :to-equal (org-reverse-datetree--encode-time
295 | (append '(0 0 0 1 4 2021)
296 | (seq-drop (decode-time (current-time)) 6)))))
297 |
298 | (it "returns the date when the entry is a descendant"
299 | (expect (org-reverse-datetree-test-with-file "test/month.org"
300 | (goto-char (point-min))
301 | (search-forward "***** B.1")
302 | (org-reverse-datetree-guess-date))
303 | :to-equal (org-reverse-datetree--encode-time
304 | (append '(0 0 0 1 4 2021)
305 | (seq-drop (decode-time (current-time)) 6)))))
306 |
307 | (it "returns a decoded time when :decoded is non-nil"
308 | (expect (org-reverse-datetree-test-with-file "test/month.org"
309 | (goto-char (point-min))
310 | (search-forward "**** B")
311 | (thread-first
312 | (org-reverse-datetree-guess-date :decoded t)
313 | (seq-take 6)))
314 | :to-equal '(nil nil nil 1 4 2021))))
315 |
316 | (describe "org-reverse-datetree-date-child-p"
317 |
318 | (it "non-nil on a direct child"
319 | (expect (org-reverse-datetree-test-with-file "test/month.org"
320 | (goto-char (point-min))
321 | (search-forward "*** 2021-04-02 Friday")
322 | (org-reverse-datetree-date-child-p))
323 | :to-be nil))
324 |
325 | (it "returns nil on a date"
326 | (expect (org-reverse-datetree-test-with-file "test/month.org"
327 | (goto-char (point-min))
328 | (search-forward "**** B")
329 | (org-reverse-datetree-date-child-p))
330 | :to-be-truthy))
331 |
332 | (it "returns nil on a descendant"
333 | (expect (org-reverse-datetree-test-with-file "test/month.org"
334 | (goto-char (point-min))
335 | (search-forward "***** B.1")
336 | (org-reverse-datetree-date-child-p))
337 | :to-be nil)))
338 |
339 | (provide 'org-reverse-datetree-test)
340 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | # -*- mode: org; mode: org-make-toc -*-
2 | * org-reverse-datetree
3 | :PROPERTIES:
4 | :TOC: :include descendants :depth 2
5 | :END:
6 | [[https://melpa.org/#/org-reverse-datetree][https://melpa.org/packages/org-reverse-datetree-badge.svg]]
7 | [[https://stable.melpa.org/#/org-reverse-datetree][https://stable.melpa.org/packages/org-reverse-datetree-badge.svg]]
8 | [[https://github.com/akirak/org-reverse-datetree/workflows/CI/badge.svg][https://github.com/akirak/org-reverse-datetree/workflows/CI/badge.svg]]
9 |
10 | This package provides functions for creating reverse
11 | date trees, which are similar to date trees as supported by built-in
12 | functions of Org mode (e.g. =org-capture=) but in a
13 | reversed order. Since newer contents come first in reverse date trees,
14 | they are more useful in situations where you want to find latest
15 | activities on a particular subject using a search tool like
16 | [[https://github.com/alphapapa/helm-org-rifle][helm-org-rifle]].
17 |
18 | [[file:screenshots/org-reverse-datetree-1.png]]
19 |
20 | :CONTENTS:
21 | - [[#features][Features]]
22 | - [[#installation][Installation]]
23 | - [[#usage][Usage]]
24 | - [[#configuring-date-formats][Configuring date formats]]
25 | - [[#defining-a-capture-template][Defining a capture template]]
26 | - [[#jumping-to-a-particular-date][Jumping to a particular date]]
27 | - [[#calendar-integration][Calendar integration]]
28 | - [[#retrieving-the-current-date-in-the-date-tree][Retrieving the current date in the date tree]]
29 | - [[#defining-a-refile-function][Defining a refile function]]
30 | - [[#archiving][Archiving]]
31 | - [[#defining-an-agenda-command][Defining an agenda command]]
32 | - [[#cleaning-up-empty-dates][Cleaning up empty dates]]
33 | - [[#defining-a-custom-org-ql-predicate][Defining a custom org-ql predicate]]
34 | - [[#configuration-examples][Configuration examples]]
35 | - [[#changelog][Changelog]]
36 | - [[#license][License]]
37 | :END:
38 | ** Features
39 | - Reverse date trees, where latest contents are shown first.
40 | - You can customize the format of the date tree.
41 | - Week trees are also supported. You can even create date trees with four levels (year-month-week-date) or any number of levels.
42 | - Configurations of date trees are stored in file headers, so each file is ensured to have a single date tree with a consistent structure.
43 | - Configuration is done interactively on first creation.
44 | ** Installation
45 | You can install this package from MELPA.
46 | ** Usage
47 | The following functions retrieve a configuration from the file header:
48 |
49 | - Use =org-reverse-datetree-goto-date-in-file= to jump to a date in the date tree. If this function is called non-interactively and the time argument is nil, it jumps to the current date. This can be used for =org-capture=.
50 | - =org-reverse-datetree-goto-read-date-in-file= is similar as above, but it always prompts for a date even if the function is called non-interactively.
51 | - =org-reverse-datetree-refile-to-file= is a function that refiles the current entry into a date tree. This can be used to build a custom command for refiling an entry to a particular file.
52 | *** Configuring date formats
53 | The format configuration is stored in the file header of each Org file, as shown in the following example:
54 |
55 | #+begin_src org
56 | ,#+REVERSE_DATETREE_DATE_FORMAT: %Y-%m-%d %A
57 | ,#+REVERSE_DATETREE_WEEK_FORMAT: %Y W%W
58 | ,#+REVERSE_DATETREE_YEAR_FORMAT: %Y
59 | ,#+REVERSE_DATETREE_USE_WEEK_TREE: t
60 | #+end_src
61 |
62 | These attributes are added by functions in this package on initial creation of the date tree, so you usually don't have to manually edit them.
63 |
64 | You can customize the default format by setting
65 | =org-reverse-datetree-{year,month,week,date}-format=.
66 | Note that the formats should be basically numeric and zero-prefixed, since
67 | date-tree headings are ordered lexicographically by their texts.
68 | You should avoid a month format starting with a string like "Feb" or "February". If you want to contain one, you should append it to a zero-prefixed numeric month.
69 |
70 | Another way to configure the structure is to set =org-reverse-datetree-level-formats= variable as a file-local variable. Through the variable, you can define a structure with any number of levels.
71 | For example, the following configuration enables date trees consisting of four levels (year-month-week-date) in all files (thanks [[https://github.com/samspo][@samspo]] for [[https://github.com/akirak/org-reverse-datetree/issues/4][reporting]]):
72 |
73 | #+begin_src emacs-lisp
74 | (setq-default org-reverse-datetree-level-formats
75 | '("%Y" ; year
76 | (lambda (time) (format-time-string "%Y-%m %B" (org-reverse-datetree-monday time))) ; month
77 | "%Y W%W" ; week
78 | "%Y-%m-%d %A" ; date
79 | ))
80 | #+end_src
81 | **** Non-reverse date tree
82 | Even though this package is named =org-reverse-datetree=, it is now possible to create a non-reverse date tree, i.e. a normal ascending date tree.
83 |
84 | To enable the feature, set =org-reverse-datetree-non-reverse= variable to non-nil. It is a file-local variable. The default continues to be a reverse date tree.
85 | *** Defining a capture template
86 | You can define an =org-capture= template which inserts an entry into a date tree with [[https://github.com/akirak/org-starter][org-starter]] package as follows:
87 |
88 | #+begin_src emacs-lisp
89 | (org-starter-def-capture "p"
90 | "Commonplace book plain entry"
91 | entry
92 | (file+function "cpb.org" org-reverse-datetree-goto-date-in-file)
93 | "* %?"
94 | :clock-in t :clock-resume t :empty-lines 1)
95 | #+end_src
96 |
97 | Or with [[https://github.com/progfolio/doct][doct]]:
98 |
99 | #+begin_src emacs-lisp
100 | (setq org-capture-templates
101 | (doct '(("Commonplace book" :keys "c"
102 | :file "~/org/cpb.org"
103 | :function org-reverse-datetree-goto-date-in-file
104 | :template ("* %?")))))
105 | #+end_src
106 | **** :olp option
107 | Warning: This is an experimental feature, so advanced features such as refiling, archiving, and cleaning up (which are described later) are not supported for it.
108 |
109 | If you want a date tree under an outline path (like =file+olp+datetree= target in =org-capture=), call the function with =:olp= option:
110 |
111 | #+begin_src emacs-lisp
112 | (org-reverse-datetree-goto-date-in-file nil :olp '("Group" "Subgroup 1"))
113 | #+end_src
114 |
115 | which you could use in a capture template like this:
116 |
117 | #+begin_src emacs-lisp
118 | (setq org-capture-templates
119 | '(("c" "Commonplace book" entry
120 | (file+function "cpb.org"
121 | (lambda ()
122 | (org-reverse-datetree-goto-date-in-file
123 | nil :olp '("Group" "Subgroup 1"))))
124 | "* %?"
125 | :clock-in t :clock-resume t)))
126 | #+end_src
127 |
128 | When a new olp is created, it is ordered alphabetically (or lexicographically).
129 |
130 | *** Jumping to a particular date
131 | Use =org-reverse-datetree-goto-date-in-file= command to jump to a particular date in the date tree of the current file.
132 | *** Calendar integration
133 | If you run =org-reverse-datetree-calendar= from an Org file, =calendar= is shown with dates in the date tree highlighted.
134 |
135 | [[file:screenshots/calendar.png]]
136 |
137 | If you run =org-reverse-datetree-display-entry= in the calendar, a corresponding date entry in the date tree will be displayed in a window. If the date doesn't exist in the date tree, a new entry will be created.
138 |
139 | To navigate between highlighted dates in the calendar, use =org-reverse-datetree-calendar-next= and =org-reverse-datetree-calendar-previous=.
140 |
141 | The following is an example configuration for =calendar-mode-map=:
142 |
143 | #+begin_src emacs-lisp
144 | (define-key calendar-mode-map "]" #'org-reverse-datetree-calendar-next)
145 | (define-key calendar-mode-map "[" #'org-reverse-datetree-calendar-previous)
146 | (define-key calendar-mode-map (kbd "RET") #'org-reverse-datetree-display-entry)
147 | #+end_src
148 |
149 | If you want to remove the highlights, run =org-reverse-datetree-unlink-calendar= command.
150 | *** Retrieving the current date in the date tree
151 | With =org-reverse-datetree-guess-date= function, you can retrieve the date of the entry at point where a date tree is effective.
152 | Note that this function may not work in certain situations, so it should be considered experimental.
153 | *** Defining a refile function
154 | With =org-reverse-datetree-refile-to-file=, you can define a function which can be used to refile entries to the date tree in a particular file:
155 |
156 | #+begin_src emacs-lisp
157 | (defun akirak/org-refile-to-cpb (arg)
158 | (interactive "P")
159 | (org-reverse-datetree-refile-to-file "~/org/cpb.org" arg))
160 | #+end_src
161 |
162 | The date is determined according to =org-reverse-datetree-entry-time= custom variable.
163 | If a =C-u= prefix argument is given, the user is asked to pick a date manually.
164 |
165 | The second argument can be an Emacs time.
166 | The following example refiles the current entry to today:
167 |
168 | #+begin_src emacs-lisp
169 | (defun akirak/org-refile-to-cpb-today (arg)
170 | (interactive "P")
171 | (org-reverse-datetree-refile-to-file "~/org/cpb.org" (current-time)))
172 | #+end_src
173 |
174 | The second argument can also take the same format as =org-reverse-datetree-entry-time=.
175 | The following function refile the current entry according to =CREATED_AT= property or the earliest clock:
176 |
177 | #+begin_src emacs-lisp
178 | (defun akirak/org-refile-to-cpb-2 (arg)
179 | (interactive "P")
180 | (org-reverse-datetree-refile-to-file "~/org/cpb.org"
181 | '((property "CREATED_AT")
182 | (clock earliest))))
183 | #+end_src
184 |
185 | You can use this function both in =org-mode= (either on a single entry or on multiple entries under selection) and in =org-agenda-mode= (either on a single entry or on bulk entries). It retrieves a date for each entry if it operates on multiple entries.
186 |
187 | [[https://github.com/akirak/org-starter][org-starter]] package integrates with this function well.
188 | For example, you can define the following function:
189 |
190 | #+begin_src emacs-lisp
191 | (defun akirak/org-refile-to-cpb (&optional arg)
192 | (interactive "P")
193 | (org-reverse-datetree-refile-to-file (org-starter-locate-file "cpb.org" nil t)
194 | arg))
195 | #+end_src
196 |
197 | A recommended way to invoke this command is to add an entry to =org-starter-extra-refile-map= in org-starter package:
198 |
199 | #+begin_src emacs-lisp
200 | (add-to-list 'org-starter-extra-refile-map
201 | '("p" akirak/org-refile-to-cpb "cpb"))
202 | #+end_src
203 |
204 | Then you can run =org-starter-refile-by-key= and press ~p~ key to refile the selected entries to =cpb.org=.
205 |
206 | The following snippet is a naive implementation of a function which migrates entries in a date-tree file (the current buffer) to another date-tree file (=dest-file= argument):
207 |
208 | #+begin_src emacs-lisp
209 | (defun org-reverse-datetree-migrate-to-file (dest-file)
210 | (let ((depth (length (org-reverse-datetree--get-level-formats)))
211 | ;; Prevent from showing the contexts for better performance
212 | (org-reverse-datetree-show-context-detail nil))
213 | (save-restriction
214 | (widen)
215 | (while (re-search-forward (rx-to-string `(and bol
216 | ,(make-string depth ?\*)
217 | space))
218 | nil t)
219 | (let ((date (thread-last (seq-drop (parse-time-string
220 | (org-get-heading t t t t))
221 | 3)
222 | (append '(0 0 0))
223 | (encode-time))))
224 | (if date
225 | (progn
226 | (outline-next-heading)
227 | (while (= (1+ depth) (org-outline-level))
228 | (org-reverse-datetree-refile-to-file dest-file date)))
229 | (user-error "Date is unavailable")))))))
230 | #+end_src
231 | *** Archiving
232 | :PROPERTIES:
233 | :CREATED_TIME: [2020-03-25 Wed 19:04]
234 | :END:
235 | You can archive a tree to a reverse datetree using =org-reverse-datetree-archive-subtree= command.
236 | It also works on multiple trees in an active region.
237 |
238 | The default destination can be customized by either setting
239 | =org-reverse-datetree-archive-file= custom variable or
240 | =REVERSE_DATETREE_ARCHIVE_FILE= property. The value should be a file path. The
241 | property can be set in an entry property (inherited) or in the file header.
242 | If none of the them are set, the function interactively prompts for a file name.
243 |
244 | From inside =org-agenda=, you can use =org-reverse-datetree-agenda-archive=.
245 | It doesn't work on bulk entries for now.
246 | *** Defining an agenda command
247 | With [[https://github.com/alphapapa/org-ql][org-ql]] package, you can define a function for browsing entries in a reverse date tree:
248 |
249 | #+begin_src emacs-lisp
250 | (org-ql-search "~/org/cpb.org"
251 | (level 4)
252 | :sort priority)
253 | #+end_src
254 |
255 | You can also define a custom org-agenda command:
256 |
257 | #+begin_src emacs-lisp
258 | (setq org-agenda-custom-commands
259 | '(("c" "Browse entries in cpb.org"
260 | org-ql-block '(level 4)
261 | ((org-super-agenda-groups
262 | '((:todo "DONE")
263 | (:todo t)))
264 | (org-agenda-files '("~/org/cpb.org"))))))
265 | #+end_src
266 |
267 | =org-super-agenda-groups= is an option for [[https://github.com/alphapapa/org-super-agenda][org-super-agenda]] for grouping the contents. If you don't activate =org-super-agenda-mode=, that option is simply ignoerd.
268 | *** Cleaning up empty dates
269 | You can use =org-reverse-datetree-cleanup-empty-dates= command to clean up date entries that contains no children.
270 | *** Defining a custom org-ql predicate
271 | =org-reverse-datetree-date-child-p= function returns non-nil if and only if the heading is a direct child of a date heading in the date tree.
272 | You can use this function to define an [[https://github.com/alphapapa/org-ql][org-ql]] predicate that matches direct children of date trees:
273 |
274 | #+begin_src emacs-lisp
275 | (org-ql-defpred datetree ()
276 | "Return non-nil if the entry is a direct child of a date entry."
277 | :body
278 | (org-reverse-datetree-date-child-p))
279 | #+end_src
280 |
281 | The following code displays entries in the date tree using =org-ql-search=:
282 |
283 | #+begin_src emacs-lisp
284 | (org-ql-search (current-buffer)
285 | '(datetree))
286 | #+end_src
287 | ** Configuration examples
288 | - [[https://out-of-cheese-error.netlify.com/spacemacs-config#org7963676][An Annotated Spacemacs - For an org-mode workflow ·]]: Using the package in some of his =org-capture= templates for clippling URLs, notes, and tasks.
289 | ** Changelog
290 | :PROPERTIES:
291 | :TOC: :depth 0
292 | :END:
293 | *** 0.4.4 (2025-05-13)
294 | - Add ~org-reverse-datetree-archive-file~ custom variable (requested by [[https://github.com/hanschen][Hans Chen]], see [[https://github.com/akirak/org-reverse-datetree/issues/74][#74]]).
295 | *** 0.4.3 (2025-04-01)
296 | - Parse datetree settings from the entire buffer (requested by Brad Stewart, a.k.a. ~@bradmont~)
297 | - Stop using ~if-let~ and ~when-let~ which are to be deprecated on Emacs 31.
298 | *** 0.4.2.2 (2024-06-19)
299 | - Fix a bug with parsing the clocked time of the entry.
300 | *** 0.4.2.1 (2024-05-30)
301 | - Fix usage of ~message~. Thank you [[https://github.com/leotaku][Leo Gaskin]] (@leotaku). ([[https://github.com/akirak/org-reverse-datetree/pull/60][#60]])
302 | - Require Emacs 29.1.
303 | *** 0.4.2 (2022-12-03)
304 | - Allow overriding org-read-date-prefer-future for the package. (See =org-reverse-datetree-prefer-future= custom variable)
305 | - If =org-use-effective-time= is non-nil, consider =org-extend-today-until= for determining the target date
306 | - Bugfix: Don't depend on org-time-stamp-formats to support Org 9.6
307 | - Bugfix: Don't throw an error when run in a file without a datetree
308 | - Bugfix: Restore the archive time
309 | - Bugfix: Skip the property drawer when inserting a new header
310 | - Require Emacs 28.1 and Org 9.5 as minimum dependencies
311 | *** 0.4.1 (2022-09-30)
312 | - Add =org-reverse-datetree-calendar-next= and =org-reverse-datetree-calendar-previous= commands.
313 | - Fix the face of calendar highlights to support light background.
314 | - Fix quotes in =cl-case= patterns.
315 | *** 0.4 (2022-08-31)
316 | - Add =org-reverse-datetree-date-child-p= function.
317 | - Add =org-reverse-datetree-default-entry-time= function.
318 | - =org-reverse-datetree-refile-to-file= function has been changed to return the time, unless a region is active in the =org-mode= buffer or bulk mode is active in =org-agenda-mode=.
319 | *** 0.3.14 (2022-08-13)
320 | - Add calendar integration.
321 | - Add =org-reverse-datetree-dates= function.
322 | *** 0.3.13 (2022-07-31)
323 | - Optimize the header reading by narrowing.
324 | - Add =org-reverse-datetree-num-levels= function.
325 | *** 0.3.12 (2022-07-02)
326 | - Add =org-reverse-datetree-guess-date= function.
327 | *** 0.3.11.1 (2022-05-22)
328 | - Add =org-reverse-datetree-map-entries= function.
329 | *** 0.3.10 (2022-03-11)
330 | - Add =match= entry type to =org-reverse-datetree-entry-time= custom variable.
331 | *** 0.3.9.1 (2022-03-09)
332 | - Hotfix for a bug introduced in 0.3.9 (reported by Tianshu Wang ([[https://github.com/tshu-w][@tshu-w]]) at [[https://github.com/akirak/org-reverse-datetree/issues/32][#32]])
333 | *** 0.3.9 (2022-03-04)
334 | - Add =org-reverse-datetree-entry-time= to allow customizing how to determine the date.
335 | - Make =org-reverse-datetree-refile-to-file= take =t= or patterns as the time argument.
336 | *** 0.3.8 (2022-02-22)
337 | - Add =org-reverse-datetree-show-context-detail= to allow customization of the behavior.
338 | *** 0.3.7 (2022-02-14)
339 | - Add =:olp= argument to functions. (Based on a feedback from [[https://github.com/krvpal][@krvpal]] at [[https://github.com/akirak/org-reverse-datetree/issues/23][#23]].)
340 | *** 0.3.6 (2022-01-18)
341 | - Add =org-reverse-datetree-show-context= option.
342 | *** 0.3.5 (2020-11-28)
343 | - Fix bugs with =org-reverse-datetree-cleanup-empty-dates=.
344 | - Switch to [[https://github.com/akirak/elinter/][elinter]] for CI.
345 | *** 0.3.4 (2020-09-23)
346 | Add a function for archiving from org-agenda, =org-reverse-datetree-agenda-archive=.
347 | *** 0.3.3 (2020-03-25)
348 | Add an initial support for archiving.
349 | *** 0.3.2 (2020-03-21)
350 | Add support for a non-reverse date tree.
351 | *** 0.3.1 (2020-02-24)
352 | - Fix a bunch of issues with =org-reverse-datetree-cleanup-empty-dates=. Explicitly documented the function in README.
353 | - Switch to GitHub Actions on running CI.
354 | ** License
355 | GPL v3
356 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/org-reverse-datetree.el:
--------------------------------------------------------------------------------
1 | ;;; org-reverse-datetree.el --- Create reverse date trees in org-mode -*- lexical-binding: t -*-
2 |
3 | ;; Copyright (C) 2018-2022,2024-2025 Akira Komamura
4 |
5 | ;; Author: Akira Komamura
6 | ;; Version: 0.4.4
7 | ;; Package-Requires: ((emacs "29.1") (dash "2.19.1") (org "9.6"))
8 | ;; Keywords: outlines
9 | ;; URL: https://github.com/akirak/org-reverse-datetree
10 |
11 | ;; This file is not part of GNU Emacs.
12 |
13 | ;;; License:
14 |
15 | ;; This program is free software: you can redistribute it and/or modify
16 | ;; it under the terms of the GNU General Public License as published by
17 | ;; the Free Software Foundation, either version 3 of the License, or
18 | ;; (at your option) any later version.
19 | ;;
20 | ;; This program is distributed in the hope that it will be useful,
21 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 | ;; GNU General Public License for more details.
24 | ;;
25 | ;; You should have received a copy of the GNU General Public License
26 | ;; along with this program. If not, see .
27 |
28 | ;;; Commentary:
29 |
30 | ;; This library provides a function for creating reverse date trees,
31 | ;; which is similar to date trees supported by `org-capture' but
32 | ;; in a reversed order. This is convenient in situation where
33 | ;; you want to find the latest status of a particular subject
34 | ;; using a search tool like `helm-org-rifle'.
35 |
36 | ;;; Code:
37 |
38 | (require 'org)
39 | (require 'subr-x)
40 | (require 'seq)
41 | (require 'cl-lib)
42 | (require 'dash)
43 |
44 | ;; Silent byte compilers
45 | (declare-function org-element-map "ext:org-element")
46 | (declare-function org-element-parse-buffer "ext:org-element")
47 | (declare-function org-element-property "ext:org-element")
48 | (declare-function org-element-clock-parser "ext:org-element")
49 | (declare-function org-element-type "ext:org-element")
50 | (defvar org-agenda-buffer-name)
51 | (defvar org-agenda-bulk-marked-entries)
52 | (defvar org-agenda-persistent-marks)
53 | (declare-function org-agenda-error "ext:org-agenda")
54 | (declare-function org-agenda-bulk-unmark-all "ext:org-agenda")
55 | (declare-function org-agenda-redo "ext:org-agenda")
56 | (declare-function org-remove-subtree-entries-from-agenda "ext:org-agenda")
57 | (declare-function org-agenda-archive-with "ext:org-agenda")
58 | (defvar org-archive-subtree-save-file-p)
59 | (defvar org-archive-save-context-info)
60 | (defvar org-archive-mark-done)
61 | (defvar org-archive-subtree-add-inherited-tags)
62 | (defvar org-archive-file-header-format)
63 | (defvar org-refile-active-region-within-subtree)
64 | (defvar org-read-date-prefer-future)
65 | (declare-function project-roots "ext:project")
66 | (declare-function project-current "ext:project")
67 | (declare-function org-inlinetask-remove-END-maybe "ext:org-inlinetask")
68 |
69 | (defgroup org-reverse-datetree nil
70 | "Reverse date trees for Org mode."
71 | :group 'org
72 | :prefix "org-reverse-datetree-")
73 |
74 | (defface org-reverse-datetree-calendar-date-face
75 | '((((class color) (min-colors 88) (background dark))
76 | :background "#481260" :foreground "#ffffff" :bold t)
77 | (((class color) (min-colors 88) (background light))
78 | :background "#eeaeee" :foreground "#000000" :bold t)
79 | (t (:background "#cd5b45")))
80 | "Face for calendar dates."
81 | :group 'calendar-faces)
82 |
83 | (defcustom org-reverse-datetree-year-format "%Y"
84 | "Year format used by org-reverse-datetree."
85 | :type '(choice string function)
86 | :group 'org-reverse-datetree)
87 |
88 | (defcustom org-reverse-datetree-month-format "%Y-%m %B"
89 | "Month format used by org-reverse-datetree."
90 | :type '(choice string function)
91 | :group 'org-reverse-datetree)
92 |
93 | (defcustom org-reverse-datetree-week-format "%Y W%W"
94 | "Week format used by org-reverse-datetree.
95 |
96 | %U is the week number starting on Sunday and %W starting on Monday."
97 | :type '(choice string function)
98 | :group 'org-reverse-datetree)
99 |
100 | (defcustom org-reverse-datetree-date-format "%Y-%m-%d %A"
101 | "Date format used by org-reverse-datetree."
102 | :type '(choice string function)
103 | :group 'org-reverse-datetree)
104 |
105 | (defcustom org-reverse-datetree-entry-time '((property "CLOSED")
106 | (clock latest)
107 | (match :type inactive))
108 | "How to determine the entry time unless explicitly specified.
109 |
110 | This is a list of patterns, and the first pattern takes
111 | precedence over the others.
112 |
113 | Each pattern takes one of the following expressions:
114 |
115 | * (property PROPERTY...)
116 |
117 | Return one of the property values, if available.
118 |
119 | PROPERTY is a string for the name of a property in the entry.
120 |
121 | You can specify, for example, \"CLOSED\".
122 |
123 | You can specify multiple values.
124 |
125 | * (clock ORDER)
126 |
127 | Return a timestamp from one of the clock entries in the logbook.
128 |
129 | ORDER can be either \\='latest or \\='earliest, which means the
130 | latest and earliest timestamp is returned respectively.
131 |
132 | * (match PLIST)
133 |
134 | Return the first match of a timestamp in the entry.
135 |
136 | PLIST can specify options."
137 | :type '(repeat
138 | (choice (cons :tag "Property"
139 | (const property)
140 | (repeat string))
141 | (list :tag "Clock entry in the drawer"
142 | (const clock)
143 | (choice (const latest)
144 | (const earliest)))
145 | (cons :tag "First regexp match of timestamp"
146 | (const match)
147 | (plist :tag "Properties"
148 | :option
149 | ((list :tag "Type of timestamp"
150 | (const :type)
151 | (choice (const :tag "Inactive timestamps (default)"
152 | inactive)
153 | (const :tag "Active timestamps"
154 | active)
155 | (const :tag "Active and inactive timestamps"
156 | any))))))))
157 | :group 'org-reverse-datetree)
158 |
159 | (defcustom org-reverse-datetree-find-function
160 | 'org-reverse-datetree--find-or-insert
161 | "Function used to find a location of a date tree or insert it."
162 | :type '(choice (symbol org-reverse-datetree--find-or-insert)
163 | (symbol org-reverse-datetree--find-or-prepend))
164 | :group 'org-reverse-datetree)
165 |
166 | (defcustom org-reverse-datetree-level-formats nil
167 | "List of formats for date trees.
168 |
169 | This setting affects the behavior of
170 | `org-reverse-datetree-goto-date-in-file' and
171 | `org-reverse-datetree-goto-read-date-in-file'.
172 |
173 | Each item in this variable corresponds to each level in date
174 | trees. Note that this variable is buffer-local, so you can also
175 | set it either as a file-local variable or as a directory-local
176 | variable.
177 |
178 | If this variable is non-nil, it take precedence over the settings
179 | in the Org header."
180 | :type '(repeat (choice string
181 | function))
182 | :group 'org-reverse-datetree
183 | :safe nil)
184 |
185 | (make-variable-buffer-local 'org-reverse-datetree-level-formats)
186 |
187 | (defcustom org-reverse-datetree-archive-file nil
188 | "File name for archiving entries.
189 |
190 | This variable is used as the destination of
191 | `org-reverse-datetree-archive-subtree'.
192 |
193 | If the variable is nil, @='REVERSE_DATETREE_ARCHIVE_FILE
194 | property (either in an Org entry of the header of the Org file) will be
195 | used instead. If none of the variable is nil, the function will ask for
196 | a file name via an interactive prompt."
197 | :group 'org-reverse-datetree
198 | :type '(choice (const nil)
199 | (file :tag "Name of an Org file")))
200 |
201 | (defcustom org-reverse-datetree-show-context-detail
202 | '((default . ancestors))
203 | "Alist that defines how to show the context of the date entry.
204 |
205 | This is a list of (RETURN-TYPE . DETAIL) items where RETURN-TYPE
206 | is an argument of `org-reverse-datetree-2' and DETAIL is a
207 | visibility span as used in `org-show-context-detail'. When there
208 | is no entry matching the return type of no return type is given,
209 | `default' will be used.
210 |
211 | Depending on the return type, the context is shown after jumping
212 | to a date tree.
213 |
214 | If this variable is nil, no explicit operation to show the
215 | context is performed, which is faster. It would be useful to
216 | temporarily set the variable to nil in scripting, e.g. for
217 | refiling many entries to a single file."
218 | :group 'org-reverse-datetree
219 | :type '(alist :key-type (choice (const marker)
220 | (const point)
221 | (const rfloc)
222 | (const created)
223 | (const default))
224 | :value-type (choice (const minimal)
225 | (const local)
226 | (const ancestors)
227 | (const lineage)
228 | (const tree)
229 | (const canonical))))
230 |
231 | (defcustom org-reverse-datetree-prefer-future 'default
232 | "Whether to assume a future date for incomplete date input.
233 |
234 | This variable overrides `org-read-date-prefer-future' inside the
235 | package.
236 |
237 | When the user enters an incomplete date, e.g. a month and a day
238 | but without a year, it may be either a future or a past date. If
239 | this variable is t, the package assumes it is a future date (in
240 | the next month or in the next year). If this variable is nil, the
241 | package assumes it is a past date (in this month or year).
242 |
243 | If the user uses the package archiving, nil value is recommended.
244 |
245 | A special value \\='default it uses the current value
246 | of `org-read-date-prefer-future'."
247 | :type '(choice (const :tag "Future (check month and day)" t)
248 | (const :tag "Past (never)" nil)
249 | (const :tag "Use `org-read-date-prefer-future'" default)))
250 |
251 | (defvar-local org-reverse-datetree--file-headers nil
252 | "Alist of headers of the buffer.")
253 |
254 | (defvar-local org-reverse-datetree-non-reverse nil
255 | "If non-nil, creates a non-reverse date tree.")
256 |
257 | (defvar-local org-reverse-datetree-num-levels nil)
258 |
259 | (eval-and-compile
260 | (if (version< emacs-version "27")
261 | (defun org-reverse-datetree--encode-time (time)
262 | "Encode TIME using `encode-time'."
263 | (apply #'encode-time time))
264 | (defalias 'org-reverse-datetree--encode-time #'encode-time)))
265 |
266 | ;;;; Common utilities
267 |
268 | (defun org-reverse-datetree--read-date ()
269 | "Wrap `org-read-date' for the package."
270 | (let ((org-read-date-prefer-future
271 | (if (eq org-reverse-datetree-prefer-future 'default)
272 | org-read-date-prefer-future
273 | org-reverse-datetree-prefer-future)))
274 | (org-read-date nil t)))
275 |
276 | ;;;; Basics
277 |
278 | (cl-defun org-reverse-datetree--find-or-prepend (level text
279 | &key append-newline
280 | &allow-other-keys)
281 | "Find or create a heading at a given LEVEL with TEXT.
282 |
283 | If APPEND-NEWLINE is non-nil, a newline is appended to the
284 | inserted text.
285 |
286 | If a new tree is created, non-nil is returned."
287 | (declare (indent 1))
288 | (let ((prefix (concat (make-string (org-get-valid-level level) ?*) " "))
289 | (bound (unless (= level 1)
290 | (save-excursion (org-end-of-subtree)))))
291 | (unless (re-search-forward (concat "^" (regexp-quote prefix) text)
292 | bound t)
293 | (if (re-search-forward (concat "^" prefix) bound t)
294 | (end-of-line 0)
295 | (end-of-line 1))
296 | (insert (concat "\n" prefix text
297 | (when append-newline
298 | "\n")))
299 | text)))
300 |
301 | (defun org-reverse-datetree--apply-format (format time)
302 | "Apply date FORMAT to TIME to produce a string.
303 |
304 | The format can be either a function or a string."
305 | (cl-etypecase format
306 | (string (format-time-string format time))
307 | (function (funcall format time))))
308 |
309 | (defun org-reverse-datetree--effective-time ()
310 | (let ((org-use-last-clock-out-time-as-effective-time nil))
311 | (org-current-effective-time)))
312 |
313 | (defun org-reverse-datetree--to-effective-time (time)
314 | "Return an effective time for TIME."
315 | (if org-use-effective-time
316 | (let ((decoded (decode-time time)))
317 | (if (and org-extend-today-until
318 | (< (nth 2 decoded) org-extend-today-until))
319 | (progn
320 | (setf (nth 2 decoded) 23)
321 | (setf (nth 1 decoded) 59)
322 | (setf (nth 0 decoded) 0)
323 | (cl-decf (nth 3 decoded))
324 | (org-reverse-datetree--encode-time decoded))
325 | time))
326 | time))
327 |
328 | ;;;###autoload
329 | (cl-defun org-reverse-datetree-2 (time level-formats
330 | &optional return-type
331 | &key asc olp)
332 | "Jump to the specified date in a reverse date tree.
333 |
334 | TIME is the date to be inserted. If omitted, it will be today.
335 |
336 | LEVEL-FORMATS is a list of formats.
337 | See `org-reverse-datetree-level-formats' for the data type.
338 |
339 | Depending on the value of RETURN-TYPE, this function returns the
340 | following values:
341 |
342 | \='marker
343 | Returns the marker of the subtree.
344 |
345 | \='point
346 | Returns point of subtree.
347 |
348 | \='rfloc
349 | Returns a refile location spec that can be used as the third
350 | argument of `org-refile' function.
351 |
352 | \='created
353 | Returns non-nil if and only if a new tree is created.
354 |
355 | If ASC is non-nil, it creates a non-reverse date tree.
356 |
357 | If OLP is a string or a list of strings, it specifies the parent
358 | tree of the date tree, like a file+olp+datetree target of
359 | `org-capture'."
360 | (unless (derived-mode-p 'org-mode)
361 | (user-error "Not in org-mode"))
362 | (save-restriction
363 | (widen)
364 | (prog1
365 | (org-save-outline-visibility t
366 | (outline-show-all)
367 | (if olp
368 | (org-reverse-datetree--olp (cl-etypecase olp
369 | (string (list olp))
370 | (list olp)))
371 | (goto-char (point-min)))
372 | (let ((parent-level (length olp)))
373 | (cl-loop for (level . format) in (-zip-pair (number-sequence
374 | (+ parent-level 1)
375 | (+ parent-level (length level-formats)))
376 | (-butlast level-formats))
377 | do (funcall org-reverse-datetree-find-function
378 | level
379 | (org-reverse-datetree--apply-format format time)
380 | :asc asc))
381 | (let ((new (funcall org-reverse-datetree-find-function (+ parent-level
382 | (length level-formats))
383 | (org-reverse-datetree--apply-format (-last-item level-formats) time)
384 | :asc asc)))
385 | (cl-case return-type
386 | (marker (point-marker))
387 | (point (point))
388 | (rfloc (list (nth 4 (org-heading-components))
389 | (buffer-file-name (or (org-base-buffer (current-buffer))
390 | (current-buffer)))
391 | nil
392 | (point)))
393 | (created new)))))
394 | (when-let* ((visibility (or (cdr (assq (or return-type 'default)
395 | org-reverse-datetree-show-context-detail))
396 | (when (not (eq return-type 'default))
397 | (cdr (assq 'default
398 | org-reverse-datetree-show-context-detail))))))
399 | (org-fold-show-set-visibility visibility)))))
400 |
401 | ;;;###autoload
402 | (cl-defun org-reverse-datetree-1 (&optional time
403 | &key
404 | week-tree
405 | return)
406 | "Jump to the specified date in a reverse date tree.
407 |
408 | This function is deprecated.
409 | Use `org-reverse-datetree-2' instead.
410 |
411 | A reverse date tree is a reversed version of the date tree in
412 | `org-capture', i.e. a date tree where the newest date is the first.
413 | This is especially useful for a notes archive, because the latest
414 | entry on a particular topic is displayed at the top in
415 | a command like `helm-org-rifle'.
416 |
417 | `org-reverse-datetree-find-function' is used to find or insert trees.
418 |
419 | TIME is the date to be inserted. If omitted, it will be today.
420 |
421 | If WEEK-TREE is non-nil, it creates week trees. Otherwise, it
422 | creates month trees.
423 |
424 | For RETURN, see the documentation of `org-reverse-datetree-2'."
425 | (unless (derived-mode-p 'org-mode)
426 | (user-error "Not in org-mode"))
427 | (let ((time (or time (org-reverse-datetree--effective-time)))
428 | (level-formats (org-reverse-datetree--level-formats
429 | (if week-tree
430 | 'week
431 | 'month))))
432 | (org-reverse-datetree-2 time level-formats return)))
433 |
434 | (make-obsolete 'org-reverse-datetree-1 'org-reverse-datetree-2 "0.3.0")
435 |
436 | (defun org-reverse-datetree--level-formats (tree-type)
437 | "Build `org-reverse-datetree-level-formats' for TREE-TYPE."
438 | (cl-ecase tree-type
439 | (month
440 | (list org-reverse-datetree-year-format
441 | org-reverse-datetree-month-format
442 | org-reverse-datetree-date-format))
443 | (week
444 | (list org-reverse-datetree-year-format
445 | org-reverse-datetree-week-format
446 | org-reverse-datetree-date-format))
447 | (month-and-week
448 | (list org-reverse-datetree-year-format
449 | org-reverse-datetree-month-format
450 | org-reverse-datetree-week-format
451 | org-reverse-datetree-date-format))))
452 |
453 | (defun org-reverse-datetree--get-level-formats (&optional allow-failure)
454 | "Return a list of outline formats for the current buffer.
455 |
456 | If ALLOW-FAILURE is non-nil, it returns nil if the buffer does
457 | not have a datetree format configured."
458 | (or org-reverse-datetree-level-formats
459 | (progn
460 | (setq org-reverse-datetree-num-levels nil)
461 | (org-reverse-datetree--get-file-headers)
462 | (catch 'datetree-format
463 | (let* ((type (org-reverse-datetree--lookup-type-header-1
464 | allow-failure))
465 | (org-reverse-datetree-year-format
466 | (or (org-reverse-datetree--lookup-format-header
467 | "REVERSE_DATETREE_YEAR_FORMAT"
468 | "Year format: "
469 | org-reverse-datetree-year-format
470 | allow-failure)
471 | (throw 'datetree-format nil)))
472 | (org-reverse-datetree-month-format
473 | (when (memq type '(month month-and-week))
474 | (or (org-reverse-datetree--lookup-format-header
475 | "REVERSE_DATETREE_MONTH_FORMAT"
476 | "Month format: "
477 | org-reverse-datetree-month-format
478 | allow-failure)
479 | (throw 'datetree-format nil))))
480 | (org-reverse-datetree-week-format
481 | (when (memq type '(week month-and-week))
482 | (or (org-reverse-datetree--lookup-format-header
483 | "REVERSE_DATETREE_WEEK_FORMAT"
484 | "Week format: "
485 | org-reverse-datetree-week-format
486 | allow-failure)
487 | (throw 'datetree-format nil))))
488 | (org-reverse-datetree-date-format
489 | (or (org-reverse-datetree--lookup-format-header
490 | "REVERSE_DATETREE_DATE_FORMAT"
491 | "Date format: "
492 | org-reverse-datetree-date-format
493 | allow-failure)
494 | (throw 'datetree-format nil)))
495 | (org-reverse-datetree-level-formats))
496 | (org-reverse-datetree--level-formats type))))))
497 |
498 | (defun org-reverse-datetree-configured-p ()
499 | "Return non-nil if the buffer has a datetree."
500 | (when (org-reverse-datetree--get-level-formats t)
501 | t))
502 |
503 | (cl-defun org-reverse-datetree--find-or-insert (level text
504 | &key asc
505 | &allow-other-keys)
506 | "Find or create a heading with the given text at the given level.
507 |
508 | LEVEL is the level of a tree, and TEXT is a heading of the tree.
509 |
510 | This function uses string comparison to compare the dates in two
511 | trees. Therefore your date format must be alphabetically ordered,
512 | e.g. beginning with YYYY(-MM(-DD)).
513 |
514 | If a new tree is created, non-nil is returned.
515 |
516 | If ASC is non-nil, it creates a date tree in ascending
517 | order i.e. non-reverse datetree."
518 | (declare (indent 1))
519 | (let* ((prefix (concat (make-string (org-get-valid-level level) ?*) " "))
520 | (bounds (delq nil (list (save-excursion
521 | (when (re-search-forward
522 | (rx bol "# Local Variables:")
523 | nil t)
524 | (line-end-position)))
525 | (unless (= level 1)
526 | (save-excursion
527 | (org-end-of-subtree))))))
528 | (bound (when bounds (-min bounds)))
529 | created
530 | found)
531 | (catch 'search
532 | (while (and (or (not bound)
533 | (> bound (point)))
534 | (re-search-forward (concat "^" (regexp-quote prefix))
535 | bound t))
536 | (let ((here (nth 4 (org-heading-components))))
537 | (cond
538 | ((string-equal here text) (progn
539 | (end-of-line 1)
540 | (setq found t)
541 | (throw 'search t)))
542 | ((if asc
543 | (string> here text)
544 | (string< here text))
545 | (progn
546 | (end-of-line 0)
547 | (org-reverse-datetree--insert-heading
548 | prefix text)
549 | (setq created t
550 | found t)
551 | (throw 'search t)))))))
552 | (unless found
553 | (goto-char (or bound (point-max)))
554 | (org-reverse-datetree--insert-heading
555 | prefix text)
556 | (setq created t))
557 | created))
558 |
559 | (defun org-reverse-datetree--insert-heading (prefix text)
560 | "Insert a heading at a particular level into the point.
561 |
562 | This function inserts a heading smartly depending on empty lines
563 | around the point.
564 |
565 | PREFIX is a prefix of the heading which consists of one or more
566 | asterisks and a space.
567 |
568 | TEXT is a heading text."
569 | ;; If the point is not at bol
570 | (unless (looking-at (rx bol))
571 | ;; If there is a blank line after the point
572 | (if (looking-at (rx (>= 2 "\n")))
573 | ;; Go to the bol immediately after the point
574 | (forward-char 1)
575 | ;; Ensure a new line is inserted
576 | (insert "\n")))
577 | (insert (if (org--blank-before-heading-p)
578 | "\n"
579 | "")
580 | prefix text))
581 |
582 | ;;;; Retrieving configuration from the file header
583 |
584 | (defun org-reverse-datetree--get-file-headers ()
585 | "Get the file headers of the current Org buffer."
586 | (org-with-wide-buffer
587 | (goto-char (point-min))
588 | (let (result)
589 | ;; This can contain unwanted keywords such as RESULTS, begin (for dynamic
590 | ;; blocks), etc., but they don't interfere with this package anyway.
591 | (while (re-search-forward org-keyword-regexp nil t)
592 | (push (cons (match-string-no-properties 1)
593 | (match-string-no-properties 2))
594 | result))
595 | (setq org-reverse-datetree--file-headers result))))
596 |
597 | (defun org-reverse-datetree--insert-header (key value)
598 | "Insert a pair of KEY and VALUE into the file header."
599 | (org-with-wide-buffer
600 | (goto-char (point-min))
601 | (when (looking-at org-property-drawer-re)
602 | (goto-char (match-end 0))
603 | (beginning-of-line 2))
604 | (if (re-search-forward (concat (rx bol "#+")
605 | (regexp-quote key)
606 | (rx ":" (1+ space)))
607 | (save-excursion
608 | (re-search-forward (rx bol "*") nil t)
609 | (point))
610 | t)
611 | (progn
612 | (kill-line)
613 | (insert value))
614 | (when (string-prefix-p "#" (thing-at-point 'line))
615 | (forward-line))
616 | (insert "#+" key ": " value "\n")
617 | ;; Update the cached value stored as a buffer-local variable
618 | (let ((pair (assoc key org-reverse-datetree--file-headers)))
619 | (if pair
620 | (setcdr pair value)
621 | (push (cons key value) org-reverse-datetree--file-headers))))))
622 |
623 | (defun org-reverse-datetree--lookup-header (key)
624 | "Look up KEY from the file headers stored as a local variable."
625 | ;; First read the headers if it has not yet.
626 | (unless org-reverse-datetree--file-headers
627 | (org-reverse-datetree--get-file-headers))
628 | (cdr (assoc key org-reverse-datetree--file-headers)))
629 |
630 | (defun org-reverse-datetree--lookup-type-header-1 (&optional fail-if-missing)
631 | "Look up a boolean file header or ask for a value.
632 |
633 | This function looks up KEY from the file headers. If the key is
634 | not contained, it asks for a new value with PROMPT, inserts the value
635 | into the header, and returns the value.
636 |
637 | If FAIL-IF-MISSING is non-nil and the key does not exist, this
638 | function returns nil."
639 | (let ((header "REVERSE_DATETREE_USE_WEEK_TREE"))
640 | (pcase (org-reverse-datetree--lookup-header header)
641 | ("month-and-week" 'month-and-week)
642 | ("t" 'week)
643 | ("nil" 'month)
644 | ('nil (unless fail-if-missing
645 | (let* ((char (read-char-choice
646 | "Choose a datetree type ([y/w] week, [n/m] month, [b] week and month): "
647 | (string-to-list "ywnmb")))
648 | (value (cl-case char
649 | ((?y ?w) 'week)
650 | ((?n ?m) 'month)
651 | ((?b) 'month-and-week))))
652 | (org-reverse-datetree--insert-header header
653 | (cl-case value
654 | (week "t")
655 | (month "nil")
656 | (month-and-week "month-and-week")))
657 | value))))))
658 |
659 | (defun org-reverse-datetree--parse-format (raw)
660 | "Parse a RAW time format string in the header.
661 |
662 | If the first character of the string is either a single quotation
663 | or an open parenthesis, it is read as a function. Otherwise, it
664 | is a string passed to `format-time-string' as the first argument."
665 | (unless (stringp raw)
666 | (user-error "Must be a string: %s" raw))
667 | (cond
668 | ((string-match (rx bol (any "'(")) raw)
669 | (read raw))
670 | (t
671 | raw)))
672 |
673 | (defun org-reverse-datetree--lookup-format-header (key prompt initial
674 | &optional fail-if-missing)
675 | "Look up a string file header or ask for a value.
676 |
677 | This function looks up KEY from the file headers. If the key is
678 | not contained, it asks for a new value with PROMPT with INITIAL
679 | as the default value, inserts the value, and returns the value.
680 |
681 | If FAIL-IF-MISSING is non-nil and the key does not exist, this
682 | function returns nil."
683 | (if-let* ((value (org-reverse-datetree--lookup-header key)))
684 | (org-reverse-datetree--parse-format (string-trim value))
685 | (unless fail-if-missing
686 | (let* ((raw (read-string prompt initial))
687 | (ret (org-reverse-datetree--parse-format raw)))
688 | (org-reverse-datetree--insert-header key raw)
689 | ret))))
690 |
691 | (defun org-reverse-datetree--lookup-string-header (key prompt initial)
692 | "Look up a string file header or ask for a value.
693 |
694 | This function looks up KEY from the file headers. If the key is
695 | not contained, it asks for a new value with PROMPT with INITIAL
696 | as the default value, inserts the value, and returns the value."
697 | (if-let* ((value (org-reverse-datetree--lookup-header key)))
698 | (string-trim value)
699 | (let ((ret (read-string prompt initial)))
700 | (org-reverse-datetree--insert-header key ret)
701 | ret)))
702 |
703 | (cl-defun org-reverse-datetree--lookup-file-name-header (key prompt
704 | &key
705 | abbreviate)
706 | "Look up a file name file header or ask for a value.
707 |
708 | This function looks up KEY from the file headers. If the key is
709 | not contained, it asks for a new value with PROMPT, inserts the
710 | value, and returns the value.
711 |
712 | If ABBREVIATE is non-nil, abbreviate the file name."
713 | (if-let* ((value (org-reverse-datetree--lookup-header key)))
714 | (string-trim value)
715 | (let ((ret (read-file-name prompt)))
716 | (org-reverse-datetree--insert-header
717 | key (org-reverse-datetree--relative-file ret abbreviate))
718 | ret)))
719 |
720 | (defun org-reverse-datetree--relative-file (dest src)
721 | "Return the relative file name of DEST from SRC or abbreviate DEST."
722 | (let* ((project-a (and (featurep 'project)
723 | (project-current nil (file-name-directory dest))))
724 | (project-b (and (featurep 'project)
725 | (project-current nil (file-name-directory src)))))
726 | (if (or (and project-a
727 | project-b
728 | (file-equal-p (car (project-roots project-a))
729 | (car (project-roots project-b))))
730 | (and (not project-a)
731 | (not project-b)
732 | (file-equal-p (file-name-directory dest)
733 | (file-name-directory src))))
734 | (file-relative-name dest (file-name-directory src))
735 | (abbreviate-file-name dest))))
736 |
737 | ;;;; Navigational commands
738 |
739 | ;;;###autoload
740 | (cl-defun org-reverse-datetree-goto-date-in-file (&optional time
741 | &key return olp)
742 | "Find or create a heading as configured in the file headers.
743 |
744 | This function finds an entry at TIME in a date tree as configured
745 | by file headers of the buffer. If there is no such configuration,
746 | ask the user for a new configuration. If TIME is omitted, it is
747 | the current date.
748 |
749 | RETURN and OLP are the same as in `org-reverse-datetree-2', which
750 | see.
751 |
752 | When this function is called interactively, it asks for TIME using
753 | `org-read-date' and go to an entry of the date."
754 | (interactive (list (org-reverse-datetree--read-date)
755 | :return nil))
756 | (unless (derived-mode-p 'org-mode)
757 | (user-error "Not in org-mode"))
758 | (org-reverse-datetree-2 time (org-reverse-datetree--get-level-formats)
759 | return
760 | :asc org-reverse-datetree-non-reverse
761 | :olp olp))
762 |
763 | ;;;###autoload
764 | (cl-defun org-reverse-datetree-goto-read-date-in-file (&rest args)
765 | "Find or create a heading as configured in the file headers.
766 |
767 | This function is like `org-reverse-datetree-goto-date-in-file',
768 | but it always asks for a date even if it is called non-interactively.
769 |
770 | ARGS are the arguments to
771 | `org-reverse-datetree-goto-date-in-file' without the time, which
772 | see."
773 | (interactive)
774 | (apply #'org-reverse-datetree-goto-date-in-file
775 | (org-reverse-datetree--read-date)
776 | (cdr args)))
777 |
778 | (defun org-reverse-datetree--timestamp-to-time (s)
779 | "Convert timestamp string S into internal time."
780 | (org-reverse-datetree--encode-time (org-parse-time-string s)))
781 |
782 | (defun org-reverse-datetree--olp (olp)
783 | "Go to an outline path in the current buffer or create it.
784 |
785 | OLP must be a list of strings."
786 | (let ((existing (copy-sequence olp))
787 | marker)
788 | (while (and existing
789 | (not (setq marker (ignore-errors (org-find-olp existing t)))))
790 | (setq existing (nbutlast existing 1)))
791 | (if marker
792 | (goto-char marker)
793 | (goto-char (point-min)))
794 | (pcase-dolist
795 | (`(,level . ,text)
796 | (-drop (length existing)
797 | (-zip-pair (number-sequence 1 (length olp))
798 | olp)))
799 | (funcall org-reverse-datetree-find-function
800 | level text :asc t))))
801 |
802 | (defun org-reverse-datetree--entry-time-2 (&optional time)
803 | "Return an Emacs time for the current Org entry.
804 |
805 | TIME can take the same value as
806 | `org-reverse-datetree-refile-to-file', which see."
807 | (pcase time
808 | (`nil
809 | (if org-reverse-datetree-entry-time
810 | (org-reverse-datetree--entry-time-2 org-reverse-datetree-entry-time)
811 | (org-reverse-datetree--read-date)))
812 | ((guard (ignore-errors (float-time time)))
813 | time)
814 | ((or `t '(4))
815 | (org-reverse-datetree--read-date))
816 | ((pred consp)
817 | (catch 'entry-time
818 | (dolist (x (copy-sequence time))
819 | (pcase x
820 | (`(property . ,props)
821 | (when-let* ((times (-some (lambda (property)
822 | (org-entry-get nil property))
823 | props)))
824 | (throw 'entry-time (org-reverse-datetree--to-effective-time
825 | (org-reverse-datetree--timestamp-to-time times)))))
826 | (`(clock ,order)
827 | (when-let* ((clocks (org-reverse-datetree--clocks)))
828 | (throw 'entry-time (when-let* ((time
829 | (cl-ecase order
830 | (latest (car (-sort (-not #'time-less-p) clocks)))
831 | (earliest (car (-sort #'time-less-p clocks))))))
832 | (org-reverse-datetree--to-effective-time time)))))
833 | (`(match . ,plist)
834 | (let ((regexp (pcase (plist-get plist :type)
835 | ('any org-ts-regexp-both)
836 | ('active org-ts-regexp)
837 | (_ org-ts-regexp-inactive)))
838 | (bound (save-excursion
839 | (org-entry-end-position))))
840 | (save-excursion
841 | (org-back-to-heading)
842 | (when (re-search-forward regexp bound t)
843 | (throw 'entry-time (org-reverse-datetree--to-effective-time
844 | (org-reverse-datetree--timestamp-to-time
845 | (match-string 1))))))))
846 | (_ (error "Unknown pattern: %s" x))))
847 | (org-reverse-datetree--read-date)))
848 | (_
849 | (error "Unsupported pattern: %s" time))))
850 |
851 | (defun org-reverse-datetree--clocks ()
852 | "Collect clocks for the current entry."
853 | (require 'org-element)
854 | (save-excursion
855 | (org-back-to-heading)
856 | (end-of-line 1)
857 | (let* ((entry-end (org-entry-end-position))
858 | (logbook-end (save-excursion
859 | (re-search-forward org-logbook-drawer-re entry-end t))))
860 | (when logbook-end
861 | (let (entries)
862 | (while (re-search-forward org-clock-line-re logbook-end t)
863 | ;; org-element-clock-parser should be called at or before the clock
864 | ;; entry.
865 | (goto-char (match-beginning 0))
866 | (let ((clock (org-element-clock-parser (line-end-position))))
867 | (when (and (eq 'clock (org-element-type clock))
868 | (eq 'closed (org-element-property :status clock)))
869 | (let ((timestamp (org-element-property :value clock)))
870 | (push (org-reverse-datetree--encode-time
871 | (list 0
872 | (org-element-property :minute-start timestamp)
873 | (org-element-property :hour-start timestamp)
874 | (org-element-property :day-start timestamp)
875 | (org-element-property :month-start timestamp)
876 | (org-element-property :year-start timestamp)
877 | nil nil nil))
878 | entries)
879 | (push (org-reverse-datetree--encode-time
880 | (list 0
881 | (org-element-property :minute-end timestamp)
882 | (org-element-property :hour-end timestamp)
883 | (org-element-property :day-end timestamp)
884 | (org-element-property :month-end timestamp)
885 | (org-element-property :year-end timestamp)
886 | nil nil nil))
887 | entries)))
888 | (end-of-line 1)))
889 | entries)))))
890 |
891 | (cl-defun org-reverse-datetree--refile-to-file (file &optional time
892 | &key ask-always prefer)
893 | "Refile the current single Org entry.
894 |
895 | This is used inside `org-reverse-datetree-refile-to-file' to
896 | refile a single tree. FILE, TIME, ASK-ALWAYS, and PREFER are the
897 | same as in the function. ASK-ALWAYS and PREFER should be removed
898 | in the future.
899 |
900 | Return the effective time of the target headline."
901 | (let* ((time (org-reverse-datetree--entry-time-2
902 | (cond
903 | (time time)
904 | (ask-always t)
905 | (prefer `(property ,@(cl-typecase prefer
906 | (list prefer)
907 | (t (list prefer))))))))
908 | (rfloc (with-current-buffer
909 | (or (find-buffer-visiting file)
910 | (find-file-noselect file))
911 | (save-excursion
912 | (org-reverse-datetree-goto-date-in-file
913 | time :return 'rfloc)))))
914 | (org-refile nil nil rfloc)
915 | time))
916 |
917 | (defun org-reverse-datetree-default-entry-time ()
918 | "Return the default expected date of the entry."
919 | (org-reverse-datetree--entry-time-2))
920 |
921 | ;;;###autoload
922 | (cl-defun org-reverse-datetree-refile-to-file (file &optional time
923 | &key ask-always prefer)
924 | "Refile the current Org entry into a configured date tree in a file.
925 |
926 | This function refiles the current entry into a date tree in FILE
927 | configured in the headers of the file. The same configuration as
928 | `org-reverse-datetree-goto-date-in-file' is used.
929 |
930 | This function retrieves a timestamp from from the entry. Unless
931 | TIME is specified, `org-reverse-datetree-entry-time' determines
932 | how to pick a timestamp. If the argument is specified, it can
933 | take the same format (i.e. a list of patterns) as
934 | `org-reverse-datetree-entry-time' variable.
935 |
936 | Alternatively, you can set TIME to t, in which case a prompt is
937 | shown to let the user choose a date explicitly.
938 |
939 | ASK-ALWAYS and PREFER are deprecated.
940 |
941 | Unless a region is active in `org-mode' or the bulk mode is
942 | active in `org-agenda-mode', this function returns the effective
943 | time of the destination entry. If either mode is effective, nil
944 | is returned."
945 | ;; NOTE: Based on org 9.3. Maybe needs updating in the future
946 | (pcase (derived-mode-p 'org-mode 'org-agenda-mode)
947 | ('org-mode
948 | (if (org-region-active-p)
949 | (let ((region-start (region-beginning))
950 | (region-end (region-end))
951 | (org-refile-active-region-within-subtree nil)
952 | subtree-end)
953 | (org-with-wide-buffer
954 | (when (file-equal-p file (buffer-file-name))
955 | (user-error "Can't refile to the same file"))
956 | (deactivate-mark)
957 | (goto-char region-start)
958 | (org-back-to-heading)
959 | (setq region-start (point))
960 | (while region-start
961 | (unless (org-at-heading-p)
962 | (org-next-visible-heading 1))
963 | (setq subtree-end (save-excursion
964 | (org-end-of-subtree)))
965 | (org-reverse-datetree--refile-to-file
966 | file time :ask-always ask-always :prefer prefer)
967 | (if (<= region-end subtree-end)
968 | (setq region-start nil)
969 | (let ((len (- subtree-end region-start)))
970 | (setq region-end (- region-end len))
971 | (goto-char region-start)))))
972 | (let ((message-log-max nil))
973 | (message "Refiled to %s" file))
974 | nil)
975 | (org-reverse-datetree--refile-to-file
976 | file time :ask-always ask-always :prefer prefer)))
977 | ('org-agenda-mode
978 | (if org-agenda-bulk-marked-entries
979 | (dolist (group (seq-group-by #'marker-buffer
980 | org-agenda-bulk-marked-entries))
981 | (let ((processed 0)
982 | (skipped 0)
983 | (d 0))
984 | (dolist (e (cl-sort (cdr group) (lambda (a b)
985 | (< (marker-position a)
986 | (marker-position b)))))
987 | (let ((pos (text-property-any (point-min) (point-max) 'org-hd-marker e))
988 | org-loop-over-headlines-in-active-region)
989 | (if (not pos)
990 | (progn
991 | (message "Skipped removed entry")
992 | (cl-incf skipped))
993 | (goto-char pos)
994 | (let* ((marker (or (org-get-at-bol 'org-hd-marker)
995 | (org-agenda-error))))
996 | (with-current-buffer (marker-buffer marker)
997 | (org-with-wide-buffer
998 | (goto-char (- (marker-position marker) d))
999 | (setq d (+ d (- (save-excursion
1000 | (org-end-of-subtree))
1001 | (point))))
1002 | (org-reverse-datetree--refile-to-file
1003 | file time :ask-always ask-always :prefer prefer))))
1004 | ;; `post-command-hook' is not run yet. We make sure any
1005 | ;; pending log note is processed.
1006 | (when (or (memq 'org-add-log-note (default-value 'post-command-hook))
1007 | (memq 'org-add-log-note post-command-hook))
1008 | (org-add-log-note))
1009 | (cl-incf processed))))
1010 | (unless org-agenda-persistent-marks (org-agenda-bulk-unmark-all))
1011 | (org-agenda-redo)
1012 | (message "Refiled %d entries to %s%s"
1013 | processed file
1014 | (if (= skipped 0)
1015 | ""
1016 | (format ", skipped %d" skipped)))
1017 | nil))
1018 | (let* ((buffer-orig (buffer-name))
1019 | (marker (or (org-get-at-bol 'org-hd-marker)
1020 | (org-agenda-error))))
1021 | (with-current-buffer (marker-buffer marker)
1022 | (org-with-wide-buffer
1023 | (goto-char (marker-position marker))
1024 | (let* (lexical-binding
1025 | (org-agenda-buffer-name buffer-orig))
1026 | (org-remove-subtree-entries-from-agenda))
1027 | (org-reverse-datetree--refile-to-file
1028 | file time :ask-always ask-always :prefer prefer))))))
1029 | (_ (user-error "Not in org-mode or org-agenda-mode"))))
1030 |
1031 | ;;;; Archiving
1032 |
1033 | ;; Based on `org-archive-subtree' in org-archive.el 9.4-dev
1034 | ;;;###autoload
1035 | (defun org-reverse-datetree-archive-subtree (&optional find-done)
1036 | "An org-reverse-datetree equivalent to `org-archive-subtree'.
1037 |
1038 | A prefix argument FIND-DONE should be treated as in
1039 | `org-archive-subtree'.
1040 |
1041 | To customize the file to which the subtree is archived, set
1042 | `org-reverse-datetree-archive-file' variable or set
1043 | @'REVERSE_DATETREE_ARCHIVE_FILE property in the file header."
1044 | (interactive "P")
1045 | (require 'org-archive)
1046 | (unless (fboundp 'org-fold-show-all)
1047 | (user-error "This function requires `org-fold-show-all' but it is unavailable"))
1048 | (if (and (org-region-active-p) org-loop-over-headlines-in-active-region)
1049 | (let ((cl (if (eq org-loop-over-headlines-in-active-region 'start-level)
1050 | 'region-start-level 'region))
1051 | org-loop-over-headlines-in-active-region)
1052 | (org-map-entries
1053 | `(progn (setq org-map-continue-from (progn (org-back-to-heading) (point)))
1054 | (org-reverse-datetree-archive-subtree ,find-done))
1055 | org-loop-over-headlines-in-active-region
1056 | cl (if (org-invisible-p) (org-end-of-subtree nil t))))
1057 | (cond
1058 | ((equal find-done '(4)) (error "FIXME: Not implemented for prefix"))
1059 | ((equal find-done '(16)) (error "FIXME: Not implemented for prefix"))
1060 | (t
1061 | ;; Save all relevant TODO keyword-related variables.
1062 | (let* ((tr-org-todo-keywords-1 org-todo-keywords-1)
1063 | (tr-org-todo-kwd-alist org-todo-kwd-alist)
1064 | (tr-org-done-keywords org-done-keywords)
1065 | (tr-org-todo-regexp org-todo-regexp)
1066 | (tr-org-todo-line-regexp org-todo-line-regexp)
1067 | (tr-org-odd-levels-only org-odd-levels-only)
1068 | (this-buffer (current-buffer))
1069 | (current-time (org-reverse-datetree--effective-time))
1070 | (file (or (buffer-file-name (buffer-base-buffer))
1071 | (error "No file associated to buffer")))
1072 | (afile (org-reverse-datetree--archive-file file))
1073 | (infile-p (file-equal-p file afile))
1074 | (newfile-p (and (org-string-nw-p afile)
1075 | (not (file-exists-p afile))))
1076 | (buffer (cond ((not (org-string-nw-p afile)) this-buffer)
1077 | ((find-buffer-visiting afile))
1078 | ((find-file-noselect afile))
1079 | (t (error "Cannot access file \"%s\"" afile))))
1080 | (closed (org-entry-get nil "CLOSED" t))
1081 | (archive-time (if closed
1082 | (and (string-match org-ts-regexp-inactive closed)
1083 | (org-reverse-datetree--encode-time
1084 | (org-parse-time-string (match-string 1 closed))))
1085 | current-time)))
1086 | (save-excursion
1087 | (org-back-to-heading t)
1088 | ;; Get context information that will be lost by moving the
1089 | ;; tree. See `org-archive-save-context-info'.
1090 | (let* ((all-tags (org-get-tags))
1091 | (local-tags
1092 | (cl-remove-if (lambda (tag)
1093 | (get-text-property 0 'inherited tag))
1094 | all-tags))
1095 | (inherited-tags
1096 | (cl-remove-if-not (lambda (tag)
1097 | (get-text-property 0 'inherited tag))
1098 | all-tags))
1099 | (context
1100 | `((category . ,(org-get-category nil 'force-refresh))
1101 | (file . ,file)
1102 | (itags . ,(mapconcat #'identity inherited-tags " "))
1103 | (ltags . ,(mapconcat #'identity local-tags " "))
1104 | (olpath . ,(mapconcat #'identity
1105 | (org-get-outline-path)
1106 | "/"))
1107 | (time . ,(format-time-string
1108 | (thread-last
1109 | (org-time-stamp-format t)
1110 | (string-remove-prefix "<")
1111 | (string-remove-suffix ">"))
1112 | current-time))
1113 | (todo . ,(org-entry-get (point) "TODO")))))
1114 | ;; We first only copy, in case something goes wrong
1115 | ;; we need to protect `this-command', to avoid kill-region sets it,
1116 | ;; which would lead to duplication of subtrees
1117 | (let (this-command) (org-copy-subtree 1 nil t))
1118 | (set-buffer buffer)
1119 | ;; Enforce Org mode for the archive buffer
1120 | (if (not (derived-mode-p 'org-mode))
1121 | ;; Force the mode for future visits.
1122 | (let ((org-insert-mode-line-in-empty-file t)
1123 | (org-inhibit-startup t))
1124 | (call-interactively #'org-mode)))
1125 | (when (and newfile-p org-archive-file-header-format)
1126 | (goto-char (point-max))
1127 | (insert (format org-archive-file-header-format
1128 | (buffer-file-name this-buffer))))
1129 | (org-reverse-datetree-goto-date-in-file archive-time)
1130 | (org-narrow-to-subtree)
1131 | ;; Force the TODO keywords of the original buffer
1132 | (let ((org-todo-line-regexp tr-org-todo-line-regexp)
1133 | (org-todo-keywords-1 tr-org-todo-keywords-1)
1134 | (org-todo-kwd-alist tr-org-todo-kwd-alist)
1135 | (org-done-keywords tr-org-done-keywords)
1136 | (org-todo-regexp tr-org-todo-regexp)
1137 | (org-todo-line-regexp tr-org-todo-line-regexp)
1138 | (org-odd-levels-only
1139 | (if (local-variable-p 'org-odd-levels-only (current-buffer))
1140 | org-odd-levels-only
1141 | tr-org-odd-levels-only)))
1142 | (goto-char (point-min))
1143 | ;; TODO: Find an alternative to `org-fold-show-all'.
1144 | ;; org-fold-show-all is unavailable in the Org shipped with
1145 | ;; Emacs 26.3.
1146 | (org-fold-show-all '(headings blocks))
1147 | ;; Paste
1148 | ;; Append to the date tree
1149 | (org-end-of-subtree)
1150 | ;; Go to the beginning of the line
1151 | (forward-line 1)
1152 | (org-paste-subtree (org-get-valid-level
1153 | (1+ (length (org-reverse-datetree--get-level-formats)))))
1154 | ;; Shall we append inherited tags?
1155 | (and inherited-tags
1156 | (or (and (eq org-archive-subtree-add-inherited-tags 'infile)
1157 | infile-p)
1158 | (eq org-archive-subtree-add-inherited-tags t))
1159 | (org-set-tags all-tags))
1160 | ;; Mark the entry as done
1161 | (when (and org-archive-mark-done
1162 | (let ((case-fold-search nil))
1163 | (looking-at org-todo-line-regexp))
1164 | (or (not (match-end 2))
1165 | (not (member (match-string 2) org-done-keywords))))
1166 | (let (org-log-done org-todo-log-states)
1167 | (org-todo
1168 | (car (or (member org-archive-mark-done org-done-keywords)
1169 | org-done-keywords)))))
1170 |
1171 | ;; Add the context info.
1172 | (dolist (item org-archive-save-context-info)
1173 | (let ((value (cdr (assq item context))))
1174 | (when (org-string-nw-p value)
1175 | (org-entry-put
1176 | (point)
1177 | (concat "ARCHIVE_" (upcase (symbol-name item)))
1178 | value))))
1179 | ;; Save and kill the buffer, if it is not the same
1180 | ;; buffer and depending on `org-archive-subtree-save-file-p'
1181 | (unless (eq this-buffer buffer)
1182 | (when (or (eq org-archive-subtree-save-file-p t)
1183 | (and (boundp 'org-archive-from-agenda)
1184 | (eq org-archive-subtree-save-file-p 'from-agenda)))
1185 | (save-buffer)))
1186 | (widen))))
1187 | ;; Here we are back in the original buffer. Everything seems
1188 | ;; to have worked. So now run hooks, cut the tree and finish
1189 | ;; up.
1190 | (run-hooks 'org-archive-hook)
1191 | (let (this-command) (org-cut-subtree))
1192 | (when (featurep 'org-inlinetask)
1193 | (org-inlinetask-remove-END-maybe))
1194 | (setq org-markers-to-move nil)
1195 | (when org-provide-todo-statistics
1196 | (save-excursion
1197 | ;; Go to parent, even if no children exist.
1198 | (org-up-heading-safe)
1199 | ;; Update cookie of parent.
1200 | (org-update-statistics-cookies nil)))
1201 | (message "Subtree archived %s"
1202 | ;; (if (eq this-buffer buffer)
1203 | ;; (concat "under heading: " heading)
1204 | ;; (concat "in file: " (abbreviate-file-name afile)))
1205 | (concat "in file: " (abbreviate-file-name afile))))))
1206 | (org-reveal)
1207 | (if (looking-at "^[ \t]*$")
1208 | (outline-next-visible-heading 1))))
1209 |
1210 | (defun org-reverse-datetree--archive-file (origin-file)
1211 | "Retrieve the name of the archive file, relative from ORIGIN-FILE."
1212 | (let ((pname "REVERSE_DATETREE_ARCHIVE_FILE"))
1213 | (or org-reverse-datetree-archive-file
1214 | (org-entry-get-with-inheritance pname)
1215 | (org-reverse-datetree--lookup-file-name-header
1216 | pname "Select an archive file: " :abbreviate origin-file))))
1217 |
1218 | ;;;###autoload
1219 | (defun org-reverse-datetree-agenda-archive ()
1220 | "Archive the entry or subtree belonging to the current agenda entry."
1221 | (interactive)
1222 | (funcall-interactively
1223 | #'org-agenda-archive-with 'org-reverse-datetree-archive-subtree))
1224 |
1225 | ;;;; Utility functions for defining formats
1226 | (defun org-reverse-datetree-monday (&optional time)
1227 | "Get Monday in the same week as TIME."
1228 | (org-reverse-datetree-last-dow 1 time))
1229 |
1230 | (defun org-reverse-datetree-sunday (&optional time)
1231 | "Get Sunday in the same week as TIME."
1232 | (org-reverse-datetree-last-dow 0 time))
1233 |
1234 | (defun org-reverse-datetree-last-dow (n &optional time)
1235 | "Get the date on N th day of week in the same week as TIME."
1236 | (let* ((time (or time (org-reverse-datetree--effective-time)))
1237 | (x (- (org-reverse-datetree--dow time) n)))
1238 | (time-add time (- (* 86400 (if (>= x 0) x (+ x 7)))))))
1239 |
1240 | (defun org-reverse-datetree--dow (time)
1241 | "Get the day of week of TIME."
1242 | (nth 6 (decode-time time)))
1243 |
1244 | ;;;; Maintenance commands
1245 |
1246 | ;;;###autoload
1247 | (cl-defun org-reverse-datetree-cleanup-empty-dates (&key noconfirm
1248 | ancestors)
1249 | "Delete empty date entries in the buffer.
1250 |
1251 | If NOCONFIRM is non-nil, nodes are deleted without confirmation.
1252 | In non-interactive mode, you have to explicitly set this
1253 | argument.
1254 |
1255 | If both NOCONFIRM and ANCESTORS are non-nil, upper level nodes
1256 | are deleted without confirmation as well."
1257 | (interactive)
1258 | (unless (derived-mode-p 'org-mode)
1259 | (user-error "Not in org-mode"))
1260 | (when (and (or noninteractive
1261 | (not (called-interactively-p 'any)))
1262 | (not noconfirm))
1263 | (error "Please set NOCONFIRM when called non-interactively"))
1264 | (let ((levels (length (org-reverse-datetree--get-level-formats t)))
1265 | count)
1266 | (when (> levels 0)
1267 | (org-save-outline-visibility t
1268 | (outline-hide-sublevels (1+ levels))
1269 | (when (or noconfirm
1270 | (and (not (org-before-first-heading-p))
1271 | (yes-or-no-p "Start from the beginning?")))
1272 | (goto-char (point-min)))
1273 | (catch 'abort
1274 | (while (> levels 0)
1275 | (setq count 0)
1276 | (while (re-search-forward
1277 | (rx-to-string `(and bol
1278 | (group (= ,levels "*")
1279 | (+ " ")
1280 | (*? nonl)
1281 | (+ "\n"))
1282 | (or string-end
1283 | (and (** 1 ,levels "*")
1284 | " "))))
1285 | nil t)
1286 | (let ((begin (match-beginning 1))
1287 | (end (match-end 1)))
1288 | (cond
1289 | (noconfirm
1290 | (delete-region begin end)
1291 | (cl-incf count)
1292 | (goto-char begin))
1293 | ((not noninteractive)
1294 | (goto-char begin)
1295 | (push-mark end)
1296 | (setq mark-active t)
1297 | (when (yes-or-no-p "Delete this empty entry?")
1298 | (call-interactively #'delete-region)
1299 | (cl-incf count)
1300 | (goto-char begin))))))
1301 | (when (= count 0)
1302 | (message "No trees were deleted. Aborting")
1303 | (throw 'abort t))
1304 | (if (and (> levels 1)
1305 | (or (and ancestors
1306 | noconfirm)
1307 | (and (not noninteractive)
1308 | (called-interactively-p 'any)
1309 | (yes-or-no-p "Clean up the upper level as well?"))))
1310 | (progn
1311 | (cl-decf levels)
1312 | (goto-char (point-min)))
1313 | (throw 'abort t))))))))
1314 |
1315 |
1316 | ;;;; Calendar integration
1317 |
1318 | (defvar org-reverse-datetree-calendar-file nil)
1319 |
1320 | (defun org-reverse-datetree-calendar ()
1321 | "Display calendar with dates in the current file highlighted.
1322 |
1323 | If the point is on a date in the date tree, go to the date in the
1324 | calendar."
1325 | (interactive)
1326 | (require 'calendar)
1327 | (org-reverse-datetree-link-calendar)
1328 | (let ((date (org-reverse-datetree-guess-date :decoded t)))
1329 | (calendar)
1330 | (when date
1331 | (calendar-goto-date (org-reverse-datetree--to-calendar-date date)))))
1332 |
1333 | (defun org-reverse-datetree-link-calendar (&optional file)
1334 | "Associate FILE with the calendar."
1335 | (unless (derived-mode-p 'org-mode)
1336 | (user-error "Run this command in org-mode"))
1337 | (setq org-reverse-datetree-calendar-file
1338 | (or file (buffer-file-name)))
1339 | (add-hook 'calendar-today-visible-hook
1340 | #'org-reverse-datetree-mark-calendar)
1341 | (add-hook 'calendar-today-invisible-hook
1342 | #'org-reverse-datetree-mark-calendar))
1343 |
1344 | (defun org-reverse-datetree-unlink-calendar ()
1345 | "Unassociate the file from the calendar."
1346 | (interactive)
1347 | (setq org-reverse-datetree-calendar-file nil)
1348 | (remove-hook 'calendar-today-visible-hook
1349 | #'org-reverse-datetree-mark-calendar)
1350 | (remove-hook 'calendar-today-invisible-hook
1351 | #'org-reverse-datetree-mark-calendar))
1352 |
1353 | (defun org-reverse-datetree-display-entry ()
1354 | "Display the Org entry for the date at point in the calendar."
1355 | (interactive)
1356 | (let ((date (calendar-cursor-to-date))
1357 | (file org-reverse-datetree-calendar-file))
1358 | (with-current-buffer (or (find-buffer-visiting file)
1359 | (find-file-noselect file))
1360 | (pop-to-buffer (current-buffer))
1361 | (org-reverse-datetree-goto-date-in-file
1362 | (org-reverse-datetree--encode-time
1363 | (list 0 0 0 (nth 1 date) (nth 0 date) (nth 2 date)
1364 | nil nil (car (current-time-zone)))))
1365 | (org-beginning-of-line))))
1366 |
1367 | (defun org-reverse-datetree-calendar-next (&optional backward)
1368 | "Go to the next date that has an entry."
1369 | (interactive)
1370 | (let ((calendar-date (calendar-cursor-to-date))
1371 | (file org-reverse-datetree-calendar-file))
1372 | (when-let* ((date (with-current-buffer (or (find-buffer-visiting file)
1373 | (find-file-noselect file))
1374 | (cl-labels
1375 | ((compare-dates (date1 date2)
1376 | (- (calendar-absolute-from-gregorian date1)
1377 | (calendar-absolute-from-gregorian date2)))
1378 | (test-pred (entry-date)
1379 | (if backward
1380 | (< (compare-dates entry-date calendar-date) 0)
1381 | (> (compare-dates entry-date calendar-date) 0)))
1382 | (sort-pred (date1 date2)
1383 | (if backward
1384 | (> (compare-dates date1 date2) 0)
1385 | (< (compare-dates date1 date2) 0))))
1386 | (thread-last
1387 | (org-reverse-datetree-dates :decoded t)
1388 | (mapcar (pcase-lambda (`(,_ ,_ ,_ ,day ,month ,year . ,_))
1389 | (list month day year)))
1390 | (seq-filter #'test-pred)
1391 | (-sort #'sort-pred)
1392 | (car))))))
1393 | (calendar-goto-date date))))
1394 |
1395 | (defun org-reverse-datetree-calendar-previous ()
1396 | "Go to the previous date that has an entry."
1397 | (interactive)
1398 | (org-reverse-datetree-calendar-next t))
1399 |
1400 | (defun org-reverse-datetree-mark-calendar ()
1401 | "Mark the calendar entry."
1402 | (when org-reverse-datetree-calendar-file
1403 | (let ((file org-reverse-datetree-calendar-file))
1404 | (dolist (date (with-current-buffer
1405 | (or (find-buffer-visiting file)
1406 | (find-file-noselect file))
1407 | (mapcar #'org-reverse-datetree--to-calendar-date
1408 | (org-reverse-datetree-dates :decoded t))))
1409 | (when (calendar-date-is-visible-p date)
1410 | (calendar-mark-visible-date date 'org-reverse-datetree-calendar-date-face))))))
1411 |
1412 | (defun org-reverse-datetree--to-calendar-date (decoded-time)
1413 | "Convert DECODED-TIME to a calendar date (month day year)."
1414 | (list (nth 4 decoded-time)
1415 | (nth 3 decoded-time)
1416 | (nth 5 decoded-time)))
1417 |
1418 | ;;;; Other public functions for convenience
1419 |
1420 | (cl-defun org-reverse-datetree-map-entries (func &key date-regexp)
1421 | "Call a function at each child of date entries.
1422 |
1423 | This is like `org-map-entries', but for the datetree. Instead of
1424 | calling a function at each headline in the buffer, it is called
1425 | at each direct child of date entries.
1426 |
1427 | FUNC is called with the date as an argument.
1428 |
1429 | If DATE-REGEXP is a string, it is used to match against the
1430 | headline text, and the matched text is given as the argument. The
1431 | entire date is skipped if the regular expression does not match.
1432 |
1433 | If DATE-REGEXP is nil, if matches all the headings at a certain
1434 | level. The entire headline of the parent date entry will be
1435 | passed to FUNC.
1436 |
1437 | It returns a list of results returned by the function."
1438 | (let* ((formats (org-reverse-datetree--get-level-formats t))
1439 | (heading-regexp (rx-to-string `(and bol
1440 | ,(make-string (length formats) ?\*)
1441 | (+ blank)
1442 | ,@(when date-regexp
1443 | `((group (regexp ,date-regexp)))))))
1444 | result)
1445 | (when formats
1446 | (while (re-search-forward heading-regexp nil t)
1447 | (let ((date (if date-regexp
1448 | (match-string-no-properties 1)
1449 | (substring-no-properties (org-get-heading t t t t))))
1450 | (level (org-get-valid-level (1+ (org-outline-level))))
1451 | (bound (save-excursion
1452 | (org-end-of-subtree))))
1453 | (while (re-search-forward org-heading-regexp bound t)
1454 | (when (= (org-outline-level) level)
1455 | (beginning-of-line)
1456 | (push (save-excursion
1457 | (funcall func date))
1458 | result))
1459 | (end-of-line)))))
1460 | (nreverse result)))
1461 |
1462 | (cl-defun org-reverse-datetree-dates (&key decoded)
1463 | "Return a list of date tree dates in the buffer.
1464 |
1465 | Unless DECODED is non-nil, the returned date is an encoded time,
1466 | so it can be passed to other functions in `org-reverse-datetree'
1467 | package. The encoded time will be the midnight in the day."
1468 | (org-with-wide-buffer
1469 | (let ((level (org-reverse-datetree-num-levels))
1470 | dates)
1471 | (goto-char (point-min))
1472 | (save-match-data
1473 | (while (re-search-forward org-complex-heading-regexp nil t)
1474 | (when (= (- (match-end 1) (match-beginning 1))
1475 | level)
1476 | (when-let* ((decoded-time (org-reverse-datetree--date
1477 | (match-string-no-properties 4))))
1478 | (org-end-of-subtree)
1479 | (push (if decoded
1480 | decoded-time
1481 | (org-reverse-datetree--encode-date decoded-time))
1482 | dates))))
1483 | dates))))
1484 |
1485 | (cl-defun org-reverse-datetree-guess-date (&key decoded)
1486 | "Return the date of the current entry in the date tree, if any.
1487 |
1488 | Note that this function may not work properly when
1489 | `org-odd-levels-only' is non-nil or the date heading is
1490 | unparsable with `parse-time-string'.
1491 |
1492 | Unless DECODED is non-nil, the returned date is an encoded time,
1493 | so it can be passed to other functions in `org-reverse-datetree'
1494 | package. The encoded time will be the midnight in the day."
1495 | (unless (org-before-first-heading-p)
1496 | (let ((level (org-reverse-datetree-num-levels))
1497 | (current-level (org-outline-level)))
1498 | (when (and level
1499 | (>= current-level level))
1500 | (org-with-wide-buffer
1501 | (when (> current-level level)
1502 | (org-up-heading-all (- current-level level)))
1503 | (when-let* ((decoded-time (org-reverse-datetree--date)))
1504 | (if decoded
1505 | decoded-time
1506 | (org-reverse-datetree--encode-date decoded-time))))))))
1507 |
1508 | (defun org-reverse-datetree-date-child-p ()
1509 | "Return non-nil if the entry is a direct child of a date entry."
1510 | (unless (org-before-first-heading-p)
1511 | (when-let* ((level (org-reverse-datetree-num-levels)))
1512 | (when (= (org-outline-level)
1513 | (1+ level))
1514 | (save-excursion
1515 | (org-up-heading-all 1)
1516 | (and (org-reverse-datetree--date) t))))))
1517 |
1518 | (defun org-reverse-datetree--date (&optional heading)
1519 | "Return the date of the heading, if any.
1520 |
1521 | This function parses the date of the heading. The date string
1522 | must contain year, month, and day of month, but the other fields
1523 | are optional.
1524 |
1525 | You can optionally give an explicit HEADING as an argument.
1526 | Otherwise, it is taken from the current Org heading."
1527 | (let ((decoded-time (ignore-errors
1528 | (parse-time-string
1529 | (or heading (org-get-heading t t t t))))))
1530 | ;; `parse-time-string' can return a decoded time that contain no date
1531 | ;; fields.
1532 | (when (and (nth 3 decoded-time)
1533 | (nth 4 decoded-time)
1534 | (nth 5 decoded-time))
1535 | decoded-time)))
1536 |
1537 | (defun org-reverse-datetree--encode-date (decoded-time)
1538 | "Return the encoded time of midnight on the date of DECODED-TIME."
1539 | ;; It may be better to add the offset of the current time zone. In
1540 | ;; that case, I would use `time-add' and `current-time-zone'.
1541 | (org-reverse-datetree--encode-time
1542 | (append '(0 0 0) (seq-drop decoded-time 3))))
1543 |
1544 | (defun org-reverse-datetree-num-levels ()
1545 | "Return the number of outline levels of datetree entries.
1546 |
1547 | If the file does not contain a datetree configured, it returns
1548 | nil.
1549 |
1550 | This uses a cached value whenever available, so it is faster than
1551 | calling `org-reverse-datetree--get-level-formats'."
1552 | (if org-reverse-datetree-level-formats
1553 | (length org-reverse-datetree-level-formats)
1554 | (let ((levels (or org-reverse-datetree-num-levels
1555 | (setq org-reverse-datetree-num-levels
1556 | (length (org-reverse-datetree--get-level-formats t))))))
1557 | ;; If the file contains no datetree, the cached value is set to zero,
1558 | (unless (= 0 levels)
1559 | levels))))
1560 |
1561 | (provide 'org-reverse-datetree)
1562 | ;;; org-reverse-datetree.el ends here
1563 |
--------------------------------------------------------------------------------