b5a82133ca
Do I really need the copyright symbol? I love how the proselint insists that I use the unicode character (which unicoding all the files sounds great to me). What could go wrong there? :-D
439 lines
16 KiB
Org Mode
439 lines
16 KiB
Org Mode
#+TITLE: My Sprint Calculations and Support
|
|
#+AUTHOR: Howard X. Abrams
|
|
#+DATE: 2020-09-25
|
|
#+FILETAGS: :emacs:
|
|
|
|
A literate program for configuring org files for work-related notes.
|
|
|
|
#+BEGIN_SRC emacs-lisp :exports none
|
|
;;; org-sprint --- Configuring org files for work-related notes. -*- lexical-binding: t; -*-
|
|
;;
|
|
;; © 2020-2022 Howard X. Abrams
|
|
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
|
|
;; See http://creativecommons.org/licenses/by/4.0/
|
|
;;
|
|
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
|
;; Maintainer: Howard X. Abrams
|
|
;; Created: September 25, 2020
|
|
;;
|
|
;; This file is not part of GNU Emacs.
|
|
;;
|
|
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
|
|
;; ~/other/hamacs/org-sprint.org
|
|
;; And tangle the file to recreate this one.
|
|
;;
|
|
;;; Code:
|
|
#+END_SRC
|
|
|
|
* Introduction
|
|
|
|
At the beginning of each Sprint, I create a new org file dedicated to it. This workflow/technique strikes a balance between a single ever-growing file, and a thousand tiny ones. This also gives me a sense of continuity, as the filename of each sprint is date-based.
|
|
|
|
I want a single keybinding that always displays the current Sprint note file, regardless of what Sprint it is. This means, I need to have functions that can calculate what this is.
|
|
|
|
In order to have the Org Capture features to be able to write to correct locations in the current file, I need each file to follow a particular format. I create a [[file:snippets/org-mode/__sprint.org][sprint note template]] that will be automatically expanded with a new sprint.
|
|
|
|
This template needs the following functions:
|
|
|
|
- =sprint-current-name= to be both the numeric label as well as the nickname
|
|
- =sprint-date-range= to include a org-formatted date range beginning and ending the sprint
|
|
- =sprint-date-from-start= return a date for pre-scheduled and recurring meetings
|
|
|
|
* Naming Sprints
|
|
|
|
I give each sprint a nickname, based on a /theme/ of some sorts, alphabetized. Since our sprints are every two weeks, this allows me to go through the alphabet once. Yeah, my group likes to boringly /number/ the sprints, so I do both...mostly for myself.
|
|
|
|
At the beginning of the year, I choose a theme, and make a list for the upcoming sprints. In the org file, this is just a list, that gets /tangled/ into an actual Emacs LIsp list. This is pretty cool.
|
|
|
|
#+BEGIN_SRC emacs-lisp :noweb yes
|
|
(defvar sprint-nicknames
|
|
(--map (replace-regexp-in-string " *[:#].*" "" (first it))
|
|
'<<sprint-names-2021()>>)
|
|
"List of 26 Sprint Nicknames from A to Z.")
|
|
#+END_SRC
|
|
** 2022
|
|
|
|
Fun sprint names for 2021 lists my favorite D&D monsters, also see [[https://list.fandom.com/wiki/List_of_monsters][this list of monsters]] from mythology and other sources:
|
|
|
|
#+NAME: sprint-names-2022
|
|
- ankheg
|
|
- beholder
|
|
- centaur
|
|
- dragon
|
|
- elf
|
|
- fetch
|
|
- goblin
|
|
- hydra
|
|
- illythid
|
|
- jackalwere
|
|
- kobold
|
|
- lich
|
|
- mimic
|
|
- nymph
|
|
- owlbear
|
|
- pegasus
|
|
- quasit
|
|
- remorhaz
|
|
- satyr
|
|
- troll
|
|
- unicorn
|
|
- vampire
|
|
- warg
|
|
- xorn
|
|
- yuan-ti
|
|
- zombie
|
|
** 2021
|
|
Choosing Sprint Names based on [[https://www.imagineforest.com/blog/funniest-words-in-the-english-language/][Funny or Silly Words]]:
|
|
|
|
#+NAME: sprint-names-2021
|
|
- abibliophobia :: The fear of running out of reading materials to read
|
|
- bamboozled :: To trick or confuse someone
|
|
- catawampus :: Something positioned diagonally
|
|
- dweeb :: A boring and uninteresting person
|
|
- eep :: Another expression of surprise or fear.
|
|
- formication :: The feeling that ants are crawling on your skin.
|
|
- goombah :: An older friend who protects you.
|
|
- hootenanny :: A country music party or get-together.
|
|
- Izzat :: This relates to your personal respect and dignity.
|
|
- jabberwock :: Something that is complete nonsense or gibberish
|
|
- kebbie :: A Scottish term relating to a walking stick with a hooked end.
|
|
- lollygagger :: Someone who walks around with no aim or goal.
|
|
- mollycoddle :: To be extra nice to someone or to overprotect them.
|
|
- nacket :: A light lunch or snack.
|
|
- obi :: A sash worn around the waist of a kimono
|
|
- panjandrum :: Someone who thinks that they are superior to others.
|
|
- quoz :: Something that is strange.
|
|
- ratoon :: The small root that sprouts from a plant, especially during the springtime.
|
|
- sialoquent :: Someone who splits while talking.
|
|
- taradiddle :: this is a small lie or when someone is speaking nonsense.
|
|
- urubu :: A blank vulture found in South American.
|
|
- vamp :: To make something brand-new.
|
|
- wabbit :: A Scottish word referring to feeling exhausted or a little unwell.
|
|
- xanthoderm :: A person with yellowish skin.
|
|
- yerk :: Pull or push something with a sudden movement.
|
|
- zazzy :: Something that is shiny and flashy
|
|
** 2020
|
|
|
|
New names from [[https://en.m.wikipedia.org/wiki/List_of_dinosaur_genera][list of dinosaurs]].
|
|
|
|
#+NAME: sprint-names-2020
|
|
- ankylosaurus
|
|
- brontosaurus
|
|
- coelophysis
|
|
- diplodocus
|
|
- eoraptor
|
|
- fruitadens
|
|
- gobiceratops
|
|
- harpymimus
|
|
- iguanodozn
|
|
- jinfengopteryx
|
|
- kentrosaurus
|
|
- lambeosaurus
|
|
- maiasaura
|
|
- neimongosaurus
|
|
- oviraptor
|
|
- pachycephalosaurus
|
|
- quetzalcoatlus
|
|
- rioarribasaurus
|
|
- stegosaurus
|
|
- tyrannosaurus
|
|
- utahraptor
|
|
- velociraptor
|
|
- wannanosaurus
|
|
- xiaotingia
|
|
- yi
|
|
- zuul
|
|
|
|
** 2019
|
|
|
|
Came up with a list of somewhat well-known cities throughout the world (at least, they had to have a population of 100,000 or more), but I didn't want any real obvious ones.
|
|
|
|
#+NAME: sprint-names-2019
|
|
- achy-aachen
|
|
- bare-bacabal
|
|
- candid-cannes
|
|
- darling-dadu
|
|
- easy-edmonton
|
|
- fancy-fargo
|
|
- gray-gaya
|
|
- handsome-hanoi
|
|
- itchy-incheon
|
|
- jumpy-juba
|
|
- kind-kindia
|
|
- less-liling
|
|
- mad-madrid
|
|
- natural-naga
|
|
- octarine-oakland
|
|
- painful-paris
|
|
- quirky-qufu
|
|
- rabid-rabat
|
|
- slow-slough
|
|
- typing-taipei
|
|
- ugly-ufa
|
|
- vibrant-vienna
|
|
- wacky-waco
|
|
- xenophobic-xichang
|
|
- yellow-yamaguchi
|
|
- zippy-zinder
|
|
|
|
* Sprint Boundaries
|
|
Function to help in calculating dates and other features of a two-week sprint that starts on Thursday and ends on a Wednesday...hey, that is just how we do things at my job.
|
|
|
|
Emacs have an internal rep of a time.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun get-date-time (date)
|
|
"Many functions can't deal with dates as string, so this will
|
|
parse DATE if it is a string, or return the value given otherwise."
|
|
(if (and date (stringp date))
|
|
(->> date ; Shame that encode-time
|
|
parse-time-string ; can't take a string, as
|
|
(-take 6) ; this seems excessive...
|
|
(--map (if (null it) 0 it))
|
|
(apply 'encode-time))
|
|
date))
|
|
#+END_SRC
|
|
|
|
** Sprint Numbering
|
|
|
|
My Sprint starts on Thursday, but this sometimes changed, so let's make this a variable:
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defvar sprint-starting-day 2 "The day of the week the sprint begins, where 0 is Sunday.")
|
|
#+END_SRC
|
|
|
|
We label our sprint based on the week number that it starts. However, on a Monday, I want to consider that we are still numbering from last week.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun sprint-week-num (&optional date)
|
|
"Return the week of the current year (or DATE), but starting
|
|
the week at Thursday to Wednesday."
|
|
(let* ((d (get-date-time date))
|
|
(dow (nth 6 (decode-time d))) ; Day of the week 0=Sunday
|
|
(week (->> d ; Week number in the year
|
|
(format-time-string "%U")
|
|
string-to-number)))
|
|
(if (>= dow sprint-starting-day)
|
|
(1+ week)
|
|
week)))
|
|
#+END_SRC
|
|
|
|
Let's have a few tests to make sure, and yeah, perhaps we update this at the beginning of each year.
|
|
|
|
#+BEGIN_SRC emacs-lisp :tangle no
|
|
(ert-deftest sprint-week-num-test ()
|
|
(should (= (sprint-week-num "2021-03-15") 11)) ;; Monday previous week
|
|
(should (= (sprint-week-num "2021-03-16") 12)) ;; Tuesday current week
|
|
(should (= (sprint-week-num "2021-03-19") 12)))
|
|
#+END_SRC
|
|
|
|
Since my sprints are currently two weeks long, we could be see that on even week numbers, the /sprint/ is actually the previous week's number.
|
|
|
|
And it appears that my PM for this year, is a week number behind.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun sprint-number (&optional date)
|
|
"Return the current sprint number, with some assumptions that
|
|
each sprint is two weeks long, starting on Thursday."
|
|
(interactive)
|
|
(let ((num (sprint-week-num date)))
|
|
(if (cl-oddp num)
|
|
(- num 2)
|
|
(- num 1))))
|
|
#+END_SRC
|
|
|
|
And some tests to verify that:
|
|
|
|
#+BEGIN_SRC emacs-lisp :tangle no
|
|
(ert-deftest sprint-number-test ()
|
|
(should (= (sprint-number "2021-03-15") 9))
|
|
(should (= (sprint-number "2021-03-16") 11))
|
|
(should (= (sprint-number "2021-03-22") 11))
|
|
(should (= (sprint-number "2021-03-23") 11))
|
|
(should (= (sprint-number "2021-03-29") 11))
|
|
(should (= (sprint-number "2021-03-30") 13)))
|
|
#+END_SRC
|
|
** Sprint File Name
|
|
|
|
I create my org-file notes based on the Sprint number.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun sprint-current-file (&optional date)
|
|
"Return the absolute pathname to the current sprint file."
|
|
(let ((d (get-date-time date)))
|
|
(expand-file-name
|
|
(format "~/Notes/Sprint-%s-%02d.org"
|
|
(format-time-string "%Y" d)
|
|
(sprint-number d)))))
|
|
#+END_SRC
|
|
|
|
So what that means, is given a particular date, I should expect to be able to find the correct Sprint file name:
|
|
|
|
#+BEGIN_SRC emacs-lisp :tangle no
|
|
(ert-deftest sprint-current-file-test ()
|
|
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-07")))
|
|
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-09")))
|
|
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-10")))
|
|
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-13")))
|
|
(should (s-ends-with? "Sprint-2019-07.org" (sprint-current-file "2019-02-14")))
|
|
(should (s-ends-with? "Sprint-2019-07.org" (sprint-current-file "2019-02-17"))))
|
|
#+END_SRC
|
|
|
|
Daily note-taking goes into my sprint file notes, so this interactive function makes an easy global short-cut key.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun sprint-current-find-file (&optional date)
|
|
"Load the `org-mode' note associated with my current sprint."
|
|
(interactive)
|
|
(let ((filename (sprint-current-file date)))
|
|
(setq org-main-file filename
|
|
org-annotate-file-storage-file filename)
|
|
(add-to-list 'org-agenda-files filename)
|
|
(find-file filename)))
|
|
#+END_SRC
|
|
|
|
The /name/ and /nickname/ of the sprint will be used in the =#+TITLE= section, and it looks something like: =Sprint 2019-07 (darling-dadu)=
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun sprint-current-name (&optional date)
|
|
"Return the default name of the current sprint (based on DATE)."
|
|
(let* ((d (get-date-time date))
|
|
(sprint-order (/ (1- (sprint-number d)) 2))
|
|
(nickname (nth sprint-order sprint-nicknames)))
|
|
(format "Sprint %s-%02d %s"
|
|
(format-time-string "%Y" d)
|
|
(sprint-number d)
|
|
nickname)))
|
|
#+END_SRC
|
|
|
|
These test won't pass any more, as the nickname of the sprint changes from year to year.
|
|
|
|
#+BEGIN_SRC emacs-lisp :tangle no
|
|
(ert-deftest sprint-current-name-test ()
|
|
(should (equal "Sprint 2019-05 (candid-cannes)" (sprint-current-name "2019-02-13")))
|
|
(should (equal "Sprint 2019-07 (darling-dadu)" (sprint-current-name "2019-02-14"))))
|
|
#+END_SRC
|
|
|
|
** Sprint Start and End
|
|
|
|
I want to print the beginning and ending of the sprint, where we have a sprint number or a data, and we can give the dates that bound the sprint. This odd function calculates this based on knowing the date of the /first thursday/ of the year, so I need to begin the year changing this value. I should fix this.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun sprint-range (&optional number-or-date)
|
|
"Return a list of three entries, start of the current sprint,
|
|
end of the current sprint, and the start of the next sprint.
|
|
Each date value should be formatted with `format-time-string'."
|
|
(let* ((num (if (or (null number-or-date) (stringp number-or-date))
|
|
(sprint-number number-or-date)
|
|
number-or-date))
|
|
(year-start "2020-01-02") ; First Thursday of the year
|
|
(time-start (-> year-start ; Converted to time
|
|
get-date-time
|
|
float-time))
|
|
(day-length (* 3600 24)) ; Length of day in seconds
|
|
(week-length (* day-length 7))
|
|
(sprint-start (time-add time-start (* week-length (1- num))))
|
|
(sprint-next (time-add time-start (* week-length (1+ num))))
|
|
(sprint-end (time-add sprint-next (- day-length))))
|
|
(list sprint-start sprint-end sprint-next)))
|
|
#+END_SRC
|
|
|
|
Format the start and end so that we can insert this directly in the org file:
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun sprint-date-range (&optional number-or-date)
|
|
"Return an `org-mode' formatted date range for a given sprint
|
|
number or date, `NUMBER-OR-DATE' or if `nil', the date range of
|
|
the current sprint."
|
|
(seq-let (sprint-start sprint-end) (sprint-range number-or-date)
|
|
(let* ((formatter "%Y-%m-%d %a")
|
|
(start (format-time-string formatter sprint-start))
|
|
(end (format-time-string formatter sprint-end)))
|
|
(format "[%s]--[%s]" start end))))
|
|
#+END_SRC
|
|
|
|
And let's have a test to validate this:
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(ert-deftest sprint-date-range ()
|
|
(should (equal (sprint-date-range 7)
|
|
(sprint-date-range "2020-02-17"))))
|
|
#+END_SRC
|
|
|
|
** Pre-scheduled Dates
|
|
|
|
Due to the regularity of the sprint cadence, I can pre-schedule meetings and other deadlines by /counting/ the number of days from the start of the sprint:
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun sprint-date-from-start (days &optional formatter)
|
|
"Given a number of DAYS from the start of the sprint, return a formatted date string."
|
|
(let* ((day-length (* 3600 24))
|
|
(start (car (sprint-range)))
|
|
(adate (time-add start (* day-length days))))
|
|
(if formatter
|
|
(format-time-string formatter adate)
|
|
(format-time-string "%Y-%m-%d %a" adate))))
|
|
#+END_SRC
|
|
|
|
* Other Date Functions
|
|
|
|
The following functions /were/ helpful at times. But I'm not sure I will use them.
|
|
|
|
#+BEGIN_SRC emacs-lisp :tangle no
|
|
(defun sprint-num-days (time-interval)
|
|
"Converts a TIME-INTERVAL to a number of days."
|
|
(let ((day-length (* 3600 24)))
|
|
(round (/ (float-time time-interval) day-length))))
|
|
#+END_SRC
|
|
|
|
#+BEGIN_SRC emacs-lisp :tangle no
|
|
(defun sprint-day-range (&optional date)
|
|
"Returns a list of two values, the number of days from the
|
|
start of the sprint, and the number of days to the end of the
|
|
sprint based on DATE if given, or from today if DATE is `nil'."
|
|
(seq-let (sprint-start sprint-end) (sprint-range date)
|
|
(let* ((now (get-date-time date))
|
|
(starting (time-subtract sprint-start now))
|
|
(ending (time-subtract sprint-end now)))
|
|
(list (sprint-num-days starting) (sprint-num-days ending)))))
|
|
#+END_SRC
|
|
|
|
#+BEGIN_SRC emacs-lisp :tangle no
|
|
(ert-deftest sprint-day-range ()
|
|
;; This sprint starts on 2/13 and ends on 2/26
|
|
(should (equal '(0 13) (sprint-day-range "2020-02-13")))
|
|
(should (equal '(-1 12) (sprint-day-range "2020-02-14")))
|
|
(should (equal '(-13 0) (sprint-day-range "2020-02-26"))))
|
|
#+END_SRC
|
|
|
|
#+BEGIN_SRC emacs-lisp :tangle no
|
|
(defun sprint-day-start (&optional date)
|
|
"Return a relative number of days to the start of the current sprint. For instance, if today was Friday, and the sprint started on Thursday, this would return -1."
|
|
(first (sprint-day-range date)))
|
|
|
|
(defun sprint-day-end (&optional date)
|
|
"Return a relative number of days to the end of the current sprint. For instance, if today was Monday, and the sprint will end on Wednesday, this would return 3."
|
|
(second (sprint-day-range date)))
|
|
#+END_SRC
|
|
|
|
* Technical Artifacts :noexport:
|
|
|
|
Let's provide a name so that the file can be required:
|
|
|
|
#+BEGIN_SRC emacs-lisp :exports none
|
|
(provide 'ha-org-sprint)
|
|
;;; ha-org-sprint.el ends here
|
|
#+END_SRC
|
|
|
|
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
|
|
|
|
#+DESCRIPTION: A literate program for configuring org files for work-related notes.
|
|
|
|
#+PROPERTY: header-args:sh :tangle no
|
|
#+PROPERTY: header-args:emacs-lisp yes
|
|
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
|
|
|
|
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
|
|
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
|
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|