├── README.md └── template.org /README.md: -------------------------------------------------------------------------------- 1 | # org-worklog 2 | 3 | This repository contains a single file, `template.org` which can be used to maintain a daily work-log via Emacs [org-mode](https://orgmode.org/). 4 | 5 | The best way to see the template is the raw view where you can see the embedded code & etc: 6 | 7 | * [template.org](https://raw.githubusercontent.com/skx/org-worklog/master/template.org) 8 | 9 | 10 | 11 | ## Features 12 | 13 | The template is pretty minimal, but still useful despite that. There is 14 | automation in-place such that you can: 15 | 16 | * Create a new entry at the start of every day. 17 | * By running `M-x new-day`. 18 | * Jump to today's entry. 19 | * By running `M-x today` 20 | * Automatically maintain a simple tag-cloud. 21 | * A table collects all the unique tags seen in the document, and allows you to show matching entries. 22 | * Reasonably nice formatting for HTML & PDF export. 23 | 24 | 25 | 26 | ## Dependencies 27 | 28 | The integrated elisp is well documented, and would require a little effort to evaluate on-load. That said there are no external dependencies. 29 | 30 | 31 | 32 | ## Bug Reports / Feature Requests 33 | 34 | Feedback is welcome, just [report an issue](https://github.com/skx/org-worklog/issues). 35 | 36 | 37 | ## Alternatives 38 | 39 | The code here has been turned into a simple package, which means you can 40 | just create a `diary.org` file and get started. 41 | 42 | * [https://github.com/skx/org-diary](https://github.com/skx/org-diary) 43 | -------------------------------------------------------------------------------- /template.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Work Log/Diary 2 | #+AUTHOR: Steve Kemp 3 | #+EMAIL: steve@steve.fi 4 | #+LATEX: \setlength\parindent{0pt} 5 | #+OPTIONS: num:nil html-postamble:nil toc:nil 6 | #+EXPORT_EXCLUDE_TAGS: noexport 7 | 8 | * Introduction 9 | For the past few years I've maintained a simple work-log, =~/Work.md=, containing notes, references, and documentation on what I've done each day. This was briefly documented [[https://blog.steve.fi/keeping_a_simple_markdown_work_log__via_emacs.html][on my blog]]. 10 | 11 | This habit has served me well, but now I'm using =org-mode=, which is part of emacs. 12 | 13 | There are many tutorials and guides relating to =org-mode=, as well as a [[https://orgmode.org/][dedicated website]] containing reference material. In terms of /this/ file there are only a few things to note: 14 | 15 | - It is written in =org-mode= syntax, which is similar to markdown. 16 | - Headers are prefixed with a =*= rather than a =#=. 17 | - Inline-code is quoted with === rather than =`=. 18 | - Everything is a tree, and you can toggle them open/closed via =TAB= & =Shift-TAB= 19 | - Because this is used within Emacs you can of course customize it with your own Lisp code. 20 | - This document contains a couple of blocks of Emacs Lisp to do different things. 21 | - Code is inline, rather than in my [[https://github.com/skx/dotfiles/blob/master/.emacs.d/init.md][emacs .dotfiles]], so this file is self-contained. 22 | 23 | **TLDR**: Every day I add a new entry, via =M-x new-day=. The new entry contains a consistent set of subheadings, and upon export any sections for a given day which are which are empty are removed. 24 | 25 | ** Implementation Notes 26 | The lisp contained at the foot of this document must be evaluated to gain access to the ability to add new entries, and jump to today's entry. 27 | 28 | My own emacs init files handle this here: 29 | 30 | - https://github.com/skx/dotfiles/blob/master/.emacs.d/init.md#org-mode-code-execution 31 | 32 | There are two things to note: 33 | 34 | - A block named =skx-startblock= is evaluated *once* when this file is loaded, if it is present. 35 | - This makes =M-x new-day=, and =M-x today= available, for example. 36 | - A block named =skx-saveblock= is evaluated *every* time this document is saved. 37 | - This updates our tag-cloud. 38 | 39 | * Achievements 40 | 41 | This is just a random section to show that you can add your own global contents, as well as the daily-log entries. 42 | 43 | ** 2019 44 | Things I achieved in 2019 were good. 45 | ** 2020 46 | Things I achieved in 2020, went viral. 47 | 48 | * Tag Cloud :noexport: 49 | The following table is automatically updated every time this document is saved, via the [[skx-saveblock]] handler. 50 | 51 | Note that this table is not exported to HTML/PDF, largely because the links wold all be horribly broken. It might be worth fixing this, to provide the data to readers. 52 | 53 | #+NAME: generate-tag-cloud 54 | #+BEGIN_SRC emacs-lisp :colnames '(Frequency Tag) :exports results 55 | (count-tags) 56 | #+END_SRC 57 | 58 | #+RESULTS: generate-tag-cloud 59 | | Frequency | Tag | 60 | |-----------+------------| 61 | | 4 | [[elisp:(org-tags-view nil "noexport")][noexport]] | 62 | | 2 | [[elisp:(org-tags-view nil "html")][html]] | 63 | | 1 | [[elisp:(org-tags-view nil "css")][css]] | 64 | | 1 | [[elisp:(org-tags-view nil "javascript")][javascript]] | 65 | | 1 | [[elisp:(org-tags-view nil "lisp")][lisp]] | 66 | 67 | 68 | 69 | * 07/10/2020 70 | ** Administrivia 71 | None. 72 | ** Desktop Setup 73 | None. 74 | ** Games 75 | None. 76 | ** Meetings 77 | None. 78 | ** Tickets / Stories / Projects 79 | Today is a random day, I resolved the following tickets: 80 | 81 | - SRE-123 82 | - SRE-124 83 | 84 | I tested some work - note in the HTML-export of this file you can collapse the following example: 85 | 86 | #+NAME: docker-test 87 | #+BEGIN_SRC sh 88 | $ docker run -t -i hello-world 89 | 90 | Hello from Docker! 91 | This message shows that your installation appears to be working correctly. 92 | 93 | To generate this message, Docker took the following steps: 94 | 1. The Docker client contacted the Docker daemon. 95 | 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. 96 | (amd64) 97 | 3. The Docker daemon created a new container from that image which runs the 98 | executable that produces the output you are currently reading. 99 | 4. The Docker daemon streamed that output to the Docker client, which sent it 100 | to your terminal. 101 | 102 | To try something more ambitious, you can run an Ubuntu container with: 103 | $ docker run -it ubuntu bash 104 | 105 | Share images, automate workflows, and more with a free Docker ID: 106 | https://hub.docker.com/ 107 | 108 | For more examples and ideas, visit: 109 | https://docs.docker.com/get-started/ 110 | #+END_SRC 111 | 112 | 113 | * 27/10/2020 114 | ** Administrivia :github: 115 | Today I published this file, in a dedicated repository: 116 | 117 | - https://github.com/skx/org-worklog 118 | ** Desktop Setup 119 | None. 120 | ** Games 121 | None. 122 | ** Meetings 123 | - 09:00-09:15 - Daily Sync 124 | ** Tickets / Stories / Projects 125 | None. 126 | ** Problems 127 | None. 128 | * DD/MM/YYYY :noexport: 129 | ** Administrivia 130 | None. 131 | ** Desktop Setup 132 | None. 133 | ** Games 134 | None. 135 | ** Meetings 136 | - 09:00-09:15 - Daily Sync 137 | ** Tickets / Stories / Projects 138 | None. 139 | ** TODO :noexport: [0%] [0/1] 140 | Entries which are not completed will be moved to the next working day, and marked as canceled. 141 | *** TODO :noexport: Time-tracking for the day 142 | ** Problems 143 | None. 144 | ** END 145 | 146 | 147 | 148 | 149 | * Export Helpers 150 | This section of our document is used when this document is exported to HTML - changing the display, and adding extra functionality. 151 | 152 | ** CSS :html:css: 153 | 154 | Here we change the way our output document is rendered when exported to HTML: 155 | 156 | - We add a fair bit of spacing between daily-entries. 157 | - New top-level headers use =H2= tags. 158 | - We indent different sub-sections. 159 | - We setup a margin of =50px;= for each child. 160 | - We insert commas between the tags in our tag-lists. 161 | 162 | #+NAME: export-css-to-rendered-output 163 | #+BEGIN_SRC org :noweb yes :exports results :results raw replace 164 | #+BEGIN_EXPORT html 165 | <> 166 | #+END_EXPORT 167 | #+END_SRC 168 | 169 | #+RESULTS: export-css-to-rendered-output 170 | #+BEGIN_EXPORT html 171 | 199 | #+END_EXPORT 200 | 201 | #+RESULTS: 202 | #+BEGIN_EXPORT html 203 | 228 | #+END_EXPORT 229 | 230 | #+NAME: display-css-for-humans 231 | #+BEGIN_SRC css :noweb yes 232 | <> 233 | #+END_SRC 234 | 235 | ** Javascript :html:javascript: 236 | The following snippet of javascript is included in our export, and does several things: 237 | 238 | - Moves the scroll-position to today's entry, if it can be found. 239 | - Updates each of our headings to prefix the date with the day of the week. 240 | - Having the weekdays is more pleasant when viewing, but entering them at the time is annoying. 241 | - Makes all source/example/code blocks collapsible. 242 | 243 | #+NAME: export-js-to-rendered-output 244 | #+BEGIN_SRC org :noweb yes :exports results :results raw replace 245 | #+BEGIN_EXPORT html 246 | <> 247 | #+END_EXPORT 248 | #+END_SRC 249 | 250 | #+RESULTS: export-js-to-rendered-output 251 | #+BEGIN_EXPORT html 252 | 366 | #+END_EXPORT 367 | 368 | #+RESULTS: 369 | #+BEGIN_EXPORT html 370 | #+END_EXPORT 371 | 372 | 373 | #+NAME: display-js-for-humans 374 | #+BEGIN_SRC javascript :noweb yes 375 | <> 376 | #+END_SRC 377 | 378 | ** Definitions :noexport: 379 | 380 | #+NAME: css-export 381 | #+BEGIN_SRC css 382 | 410 | #+END_SRC 411 | 412 | #+NAME: js-export 413 | #+BEGIN_SRC javascript 414 | 528 | #+END_SRC 529 | 530 | * Lisp Code :lisp:noexport: 531 | The following section of code allows me to work with this document more easily: 532 | 533 | - =M-x today= will jump to today's entry, if present. 534 | - =M-x new-day= will create a new entry for today, using a template, if it isn't already present. 535 | 536 | There is also some code to setup a [[Tag cloud]], which is executed when this file is saved, and a bit of magic to remove empty sections of my daily-entries when the file is exported to HTML/PDF. 537 | 538 | Before you can use this you'll need to evaluate the following two blocks of code (my dotfiles do that automatically): 539 | 540 | - [[skx-saveblock]] is executed before this file is saved. 541 | - [[skx-startblock]] is executed when this file is loaded. 542 | 543 | #+NAME: skx-saveblock 544 | #+BEGIN_SRC emacs-lisp :results output silent 545 | (skx-org-eval-named-block "generate-tag-cloud") 546 | (skx-org-eval-named-block "export-css-to-rendered-output") 547 | (skx-org-eval-named-block "export-js-to-rendered-output") 548 | 549 | ;; Align tags on save - `M-x org-align-all-tags` is deprecated, so we 550 | ;; try to use the newer alternative if it is present. 551 | (if (fboundp 'org-align-tags) 552 | (org-align-tags t) 553 | (org-align-all-tags)) 554 | #+END_SRC 555 | 556 | #+NAME: skx-startblock 557 | #+BEGIN_SRC emacs-lisp :results output silent 558 | (defun new-day () 559 | "Create a new entry for today, if one isn't already present." 560 | (interactive) 561 | (if (today) 562 | (message "Entry for today already present") 563 | (new-day-insert))) 564 | 565 | ;; List of things we expand inside the templated-section of this file. 566 | ;; The pairs are "regexp" + "replacement" which is invoked via "apply". 567 | (setq new-day-template-variables '( 568 | ( "YYYY" . (format-time-string "%Y")) 569 | ( "MM" . (format-time-string "%m")) 570 | ( "DD" . (format-time-string "%d")) 571 | ( "HOUR" . (format-time-string "%H")) 572 | ( "MINUTE" . (format-time-string "%M")) 573 | ( ":noexport:" . (format "")))) 574 | 575 | (defun new-day-insert () 576 | "Insert the contents of a template into the document, for a new day's work. 577 | 578 | This function inserts the block found between '* DD/MM/YYYY' and 'END' into the buffer, replacing 'DD', 'MM', 'YYYY' with the appropriate date-fields." 579 | (let ((start nil) 580 | (text nil) 581 | (case-fold-search nil) ; This ensures our replacements match "HOURS" not "Worked Hours" 582 | (end nil)) 583 | (save-excursion 584 | (outline-show-all) 585 | (goto-line 0) 586 | (re-search-forward "^\* DD/MM/YYYY" ) 587 | (beginning-of-line) 588 | (backward-char 1) 589 | (setq start (point)) 590 | ;; point is at the line before "* DD/MM" 591 | ;; So we want to skip forward 592 | (next-line 2) 593 | (re-search-forward "END$") 594 | (beginning-of-line) 595 | (backward-char 1) 596 | (setq end (point)) 597 | (setq text (buffer-substring start end)) 598 | (goto-char start) 599 | 600 | ; Replace all our template-pairs 601 | (dolist (item new-day-template-variables) 602 | (setq text (replace-regexp-in-string (car item) (apply (cdr item)) text))) 603 | (insert text)) 604 | (goto-char start) 605 | (outline-hide-sublevels 1) 606 | )) 607 | 608 | ;; Jump to today's entry. 609 | (defun today () 610 | "Visit today's entry, if it exists. Otherwise show a message." 611 | (interactive) 612 | (let ((pos nil)) 613 | (save-excursion 614 | (org-save-outline-visibility t 615 | (outline-show-all) 616 | (goto-line 0) 617 | (if (re-search-forward (format-time-string "^\\* %d/%m/%Y") nil t) 618 | (setq pos (point)) 619 | (message "No entry for today found.")))) 620 | (if pos 621 | (progn 622 | (outline-show-all) 623 | (goto-char pos) 624 | t) 625 | nil))) 626 | 627 | (defun clear-subtree () 628 | "Delete the subtree we're inside. 629 | 630 | We move to the start of the heading, record our position, then the 631 | end of the tree and work backwards until we've gone too far." 632 | (let (start) 633 | (save-excursion 634 | (org-back-to-heading t) 635 | (setq start (point)) 636 | (org-end-of-subtree t) 637 | (while (>= (point) start) 638 | (delete-char -1))))) 639 | 640 | (defun remove-empty-sections (backend) 641 | "If there are any headings which contain only 'empty' content 642 | then don't show them on export 643 | 644 | Empty here means either literally empty, or having the content 645 | 'None' or 'None.'." 646 | (save-excursion 647 | (outline-show-all) 648 | (goto-line 0) 649 | 650 | (org-map-entries 651 | '(lambda () 652 | (if (or (equalp "None." (format "%s" (org-get-entry))) 653 | (equalp "None" (format "%s" (org-get-entry))) 654 | (equalp "" (format "%s" (org-get-entry)))) 655 | (clear-subtree)))))) 656 | 657 | 658 | ;; Remove empty-sections on export. 659 | (add-hook 'org-export-before-parsing-hook 'remove-empty-sections) 660 | 661 | ;; Update our tag-cloud. Note that the links are "dangerous", 662 | ;; and will show errors on export to HTML/PDF, so the table must 663 | ;; be marked :noexport: 664 | (defun count-tags () 665 | "Update our tag-cloud table" 666 | (let (tags count) 667 | (save-excursion 668 | (goto-char (point-min)) 669 | (while (re-search-forward org-complex-heading-regexp nil t) 670 | (dolist (tag (org-get-tags)) 671 | (unless (equalp tag "") 672 | (push tag tags)))) 673 | (cl-loop with result 674 | for tag in tags 675 | do (push (list (cl-count tag tags 676 | :test #'string=) 677 | (format "[[elisp:(org-tags-view nil \"%s\")][%s]]" tag tag)) 678 | count) 679 | collect 680 | (setq result (cl-remove-duplicates count 681 | :test #'equal)) 682 | finally return 683 | (cl-sort result #'> :key #'car))))) 684 | 685 | 686 | ;; 687 | ;; Ensure we can follow "tag-search" / elisp links without a prompt 688 | ;; 689 | (make-variable-buffer-local 'org-confirm-elisp-link-function) 690 | (setq org-confirm-elisp-link-function nil) 691 | 692 | ;; Ensure that we can export org-blocks 693 | ;; This is done for the CSS & Javascript export blocks 694 | (require 'ob-org) 695 | 696 | #+END_SRC 697 | --------------------------------------------------------------------------------