#+TITLE: Work Log/Diary #+AUTHOR: Steve Kemp #+EMAIL: steve@steve.fi #+LATEX: \setlength\parindent{0pt} #+OPTIONS: num:nil html-postamble:nil toc:nil #+EXPORT_EXCLUDE_TAGS: noexport * Introduction 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]]. This habit has served me well, but now I'm using =org-mode=, which is part of emacs. 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: - It is written in =org-mode= syntax, which is similar to markdown. - Headers are prefixed with a =*= rather than a =#=. - Inline-code is quoted with === rather than =`=. - Everything is a tree, and you can toggle them open/closed via =TAB= & =Shift-TAB= - Because this is used within Emacs you can of course customize it with your own Lisp code. - This document contains a couple of blocks of Emacs Lisp to do different things. - 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. **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. ** Implementation Notes 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. My own emacs init files handle this here: - https://github.com/skx/dotfiles/blob/master/.emacs.d/init.md#org-mode-code-execution There are two things to note: - A block named =skx-startblock= is evaluated *once* when this file is loaded, if it is present. - This makes =M-x new-day=, and =M-x today= available, for example. - A block named =skx-saveblock= is evaluated *every* time this document is saved. - This updates our tag-cloud. * Achievements This is just a random section to show that you can add your own global contents, as well as the daily-log entries. ** 2019 Things I achieved in 2019 were good. ** 2020 Things I achieved in 2020, went viral. * Tag Cloud :noexport: The following table is automatically updated every time this document is saved, via the [[skx-saveblock]] handler. 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. #+NAME: generate-tag-cloud #+BEGIN_SRC emacs-lisp :colnames '(Frequency Tag) :exports results (count-tags) #+END_SRC #+RESULTS: generate-tag-cloud | Frequency | Tag | |-----------+------------| | 4 | [[elisp:(org-tags-view nil "noexport")][noexport]] | | 2 | [[elisp:(org-tags-view nil "html")][html]] | | 1 | [[elisp:(org-tags-view nil "css")][css]] | | 1 | [[elisp:(org-tags-view nil "javascript")][javascript]] | | 1 | [[elisp:(org-tags-view nil "lisp")][lisp]] | * 07/10/2020 ** Administrivia None. ** Desktop Setup None. ** Games None. ** Meetings None. ** Tickets / Stories / Projects Today is a random day, I resolved the following tickets: - SRE-123 - SRE-124 I tested some work - note in the HTML-export of this file you can collapse the following example: #+NAME: docker-test #+BEGIN_SRC sh $ docker run -t -i hello-world Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ #+END_SRC * 27/10/2020 ** Administrivia :github: Today I published this file, in a dedicated repository: - https://github.com/skx/org-worklog ** Desktop Setup None. ** Games None. ** Meetings - 09:00-09:15 - Daily Sync ** Tickets / Stories / Projects None. ** Problems None. * DD/MM/YYYY :noexport: ** Administrivia None. ** Desktop Setup None. ** Games None. ** Meetings - 09:00-09:15 - Daily Sync ** Tickets / Stories / Projects None. ** TODO :noexport: [0%] [0/1] Entries which are not completed will be moved to the next working day, and marked as canceled. *** TODO :noexport: Time-tracking for the day ** Problems None. ** END * Export Helpers This section of our document is used when this document is exported to HTML - changing the display, and adding extra functionality. ** CSS :html:css: Here we change the way our output document is rendered when exported to HTML: - We add a fair bit of spacing between daily-entries. - New top-level headers use =H2= tags. - We indent different sub-sections. - We setup a margin of =50px;= for each child. - We insert commas between the tags in our tag-lists. #+NAME: export-css-to-rendered-output #+BEGIN_SRC org :noweb yes :exports results :results raw replace #+BEGIN_EXPORT html <> #+END_EXPORT #+END_SRC #+RESULTS: export-css-to-rendered-output #+BEGIN_EXPORT html #+END_EXPORT #+RESULTS: #+BEGIN_EXPORT html #+END_EXPORT #+NAME: display-css-for-humans #+BEGIN_SRC css :noweb yes <> #+END_SRC ** Javascript :html:javascript: The following snippet of javascript is included in our export, and does several things: - Moves the scroll-position to today's entry, if it can be found. - Updates each of our headings to prefix the date with the day of the week. - Having the weekdays is more pleasant when viewing, but entering them at the time is annoying. - Makes all source/example/code blocks collapsible. #+NAME: export-js-to-rendered-output #+BEGIN_SRC org :noweb yes :exports results :results raw replace #+BEGIN_EXPORT html <> #+END_EXPORT #+END_SRC #+RESULTS: export-js-to-rendered-output #+BEGIN_EXPORT html #+END_EXPORT #+RESULTS: #+BEGIN_EXPORT html #+END_EXPORT #+NAME: display-js-for-humans #+BEGIN_SRC javascript :noweb yes <> #+END_SRC ** Definitions :noexport: #+NAME: css-export #+BEGIN_SRC css #+END_SRC #+NAME: js-export #+BEGIN_SRC javascript #+END_SRC * Lisp Code :lisp:noexport: The following section of code allows me to work with this document more easily: - =M-x today= will jump to today's entry, if present. - =M-x new-day= will create a new entry for today, using a template, if it isn't already present. 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. Before you can use this you'll need to evaluate the following two blocks of code (my dotfiles do that automatically): - [[skx-saveblock]] is executed before this file is saved. - [[skx-startblock]] is executed when this file is loaded. #+NAME: skx-saveblock #+BEGIN_SRC emacs-lisp :results output silent (skx-org-eval-named-block "generate-tag-cloud") (skx-org-eval-named-block "export-css-to-rendered-output") (skx-org-eval-named-block "export-js-to-rendered-output") ;; Align tags on save - `M-x org-align-all-tags` is deprecated, so we ;; try to use the newer alternative if it is present. (if (fboundp 'org-align-tags) (org-align-tags t) (org-align-all-tags)) #+END_SRC #+NAME: skx-startblock #+BEGIN_SRC emacs-lisp :results output silent (defun new-day () "Create a new entry for today, if one isn't already present." (interactive) (if (today) (message "Entry for today already present") (new-day-insert))) ;; List of things we expand inside the templated-section of this file. ;; The pairs are "regexp" + "replacement" which is invoked via "apply". (setq new-day-template-variables '( ( "YYYY" . (format-time-string "%Y")) ( "MM" . (format-time-string "%m")) ( "DD" . (format-time-string "%d")) ( "HOUR" . (format-time-string "%H")) ( "MINUTE" . (format-time-string "%M")) ( ":noexport:" . (format "")))) (defun new-day-insert () "Insert the contents of a template into the document, for a new day's work. This function inserts the block found between '* DD/MM/YYYY' and 'END' into the buffer, replacing 'DD', 'MM', 'YYYY' with the appropriate date-fields." (let ((start nil) (text nil) (case-fold-search nil) ; This ensures our replacements match "HOURS" not "Worked Hours" (end nil)) (save-excursion (outline-show-all) (goto-line 0) (re-search-forward "^\* DD/MM/YYYY" ) (beginning-of-line) (backward-char 1) (setq start (point)) ;; point is at the line before "* DD/MM" ;; So we want to skip forward (next-line 2) (re-search-forward "END$") (beginning-of-line) (backward-char 1) (setq end (point)) (setq text (buffer-substring start end)) (goto-char start) ; Replace all our template-pairs (dolist (item new-day-template-variables) (setq text (replace-regexp-in-string (car item) (apply (cdr item)) text))) (insert text)) (goto-char start) (outline-hide-sublevels 1) )) ;; Jump to today's entry. (defun today () "Visit today's entry, if it exists. Otherwise show a message." (interactive) (let ((pos nil)) (save-excursion (org-save-outline-visibility t (outline-show-all) (goto-line 0) (if (re-search-forward (format-time-string "^\\* %d/%m/%Y") nil t) (setq pos (point)) (message "No entry for today found.")))) (if pos (progn (outline-show-all) (goto-char pos) t) nil))) (defun clear-subtree () "Delete the subtree we're inside. We move to the start of the heading, record our position, then the end of the tree and work backwards until we've gone too far." (let (start) (save-excursion (org-back-to-heading t) (setq start (point)) (org-end-of-subtree t) (while (>= (point) start) (delete-char -1))))) (defun remove-empty-sections (backend) "If there are any headings which contain only 'empty' content then don't show them on export Empty here means either literally empty, or having the content 'None' or 'None.'." (save-excursion (outline-show-all) (goto-line 0) (org-map-entries '(lambda () (if (or (equalp "None." (format "%s" (org-get-entry))) (equalp "None" (format "%s" (org-get-entry))) (equalp "" (format "%s" (org-get-entry)))) (clear-subtree)))))) ;; Remove empty-sections on export. (add-hook 'org-export-before-parsing-hook 'remove-empty-sections) ;; Update our tag-cloud. Note that the links are "dangerous", ;; and will show errors on export to HTML/PDF, so the table must ;; be marked :noexport: (defun count-tags () "Update our tag-cloud table" (let (tags count) (save-excursion (goto-char (point-min)) (while (re-search-forward org-complex-heading-regexp nil t) (dolist (tag (org-get-tags)) (unless (equalp tag "") (push tag tags)))) (cl-loop with result for tag in tags do (push (list (cl-count tag tags :test #'string=) (format "[[elisp:(org-tags-view nil \"%s\")][%s]]" tag tag)) count) collect (setq result (cl-remove-duplicates count :test #'equal)) finally return (cl-sort result #'> :key #'car))))) ;; ;; Ensure we can follow "tag-search" / elisp links without a prompt ;; (make-variable-buffer-local 'org-confirm-elisp-link-function) (setq org-confirm-elisp-link-function nil) ;; Ensure that we can export org-blocks ;; This is done for the CSS & Javascript export blocks (require 'ob-org) #+END_SRC