From d3f14de2eeb756da37ff767406645edde98ea1b2 Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Fri, 5 Nov 2021 17:06:55 -0700 Subject: [PATCH] Getting work-specific code to work This includes some org-specific stuff, like my Sprint and clipboard code. --- .gitignore | 7 +- bootstrap.org | 67 +++++-- ha-org-babel.org | 78 ++++++++ ha-org-clipboard.org | 190 +++++++++++++++++++ ha-org-sprint.org | 434 +++++++++++++++++++++++++++++++++++++++++++ ha-org.org | 18 +- 6 files changed, 767 insertions(+), 27 deletions(-) create mode 100644 ha-org-babel.org create mode 100644 ha-org-clipboard.org create mode 100644 ha-org-sprint.org diff --git a/.gitignore b/.gitignore index 36da315..db83495 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ *~ -/bootstrap.el -/ha-config.el -/ha-display.el -/ha-org-word-processor.el -/ha-org.el +/*.el +/ha-work.org diff --git a/bootstrap.org b/bootstrap.org index 5138ac5..6469f40 100644 --- a/bootstrap.org +++ b/bootstrap.org @@ -57,6 +57,10 @@ The following packages come with Emacs, but seems like they still need loading: #+BEGIN_SRC emacs-lisp (require 'subr-x) #+END_SRC +Ugh. Why am I getting this error? +#+BEGIN_SRC emacs-lisp + (defun first (elt) (car elt)) +#+END_SRC While most libraries will take care of their dependencies, I want to install /my dependent libraries/. Especially, [[https://github.com/magnars/.emacs.d/][Magnar Sveen]]'s Clojure-inspired [[https://github.com/magnars/dash.el][dash.el]] project: #+BEGIN_SRC emacs-lisp (use-package dash) @@ -78,31 +82,54 @@ Much of my more complicated code comes from my website essays and other projects #+END_SRC Hopefully, this will tie me over while I transition. +** Emacs Server Control +Sure the Emacs application will almost always have the =server-start= going, however, I need to control it just a bit (because I often have two instances running on some of my machines). What /defines/ the Emacs instance for work changes ... often: + +#+BEGIN_SRC emacs-lisp +(defun ha-emacs-for-work? () + "Return non-nil when the Emacs application's location matches as one for work. +Currently, this is the `emacs-plus' app that I have built with +the native-comp model, but I reserve the right to change this." + (->> Info-default-directory-list + (first) + (s-split "/") + (--filter (s-starts-with? "emacs-plus" it)) + (first))) +#+END_SRC + +#+BEGIN_SRC emacs-lisp +(if (ha-emacs-for-work?) + (setq server-name "work") + (setq server-name "personal")) + +(server-start) +#+END_SRC * Load the Rest The following loads the rest of my org-mode literate files. I add them as they are /ready/, but eventually, I'll trim this up into a nicer pattern. #+BEGIN_SRC emacs-lisp -(dolist (file '("ha-config.org" - "ha-display.org" - "ha-org.org" - "ha-org-word-processor.org" - ;; "org-clipboard.org" - ;; "org-journaling.org" - ;; "org-publishing.org" - ;; "org-sprint.org" - ;; "capturing-notes.org" - ;; "general-programming.org" - ;; "ha-agendas.org" - ;; "ha-email.org" - ;; "ha-irc.org" - ;; "ha-passwords.org" - ;; "ha-remoting.org" - ;; "my-feeds.org" - )) - (org-babel-load-file (f-join hamacs-source-dir file))) + (dolist (file `("ha-config.org" + "ha-display.org" + "ha-org.org" + "ha-org-word-processor.org" + "ha-org-clipboard.org" + ;; "org-journaling.org" + ;; "org-publishing.org" + "ha-org-sprint.org" + ;; "capturing-notes.org" + ;; "general-programming.org" + ;; "ha-agendas.org" + ;; "ha-email.org" + ;; "ha-irc.org" + ;; "ha-passwords.org" + ;; "ha-remoting.org" + ;; "my-feeds.org" + ,(when (ha-emacs-for-work?) + "ha-work.org"))) + (org-babel-load-file (f-join hamacs-source-dir file))) #+END_SRC We can test/debug any individual file, via: -#+BEGIN_SRC emacs-lisp - (org-babel-load-file (f-join hamacs-source-dir "ha-config.org")) +#+BEGIN_SRC emacs-lisp :tangle no + (org-babel-load-file (f-join hamacs-source-dir "ha-org-word-processor.org")) #+END_SRC * Technical Artifacts :noexport: Let's provide a name so that the file can be required: diff --git a/ha-org-babel.org b/ha-org-babel.org new file mode 100644 index 0000000..e3e1f5f --- /dev/null +++ b/ha-org-babel.org @@ -0,0 +1,78 @@ +#+TITLE: Literate Programming with Org +#+AUTHOR: Howard X. Abrams +#+EMAIL: howard.abrams@gmail.com +#+DATE: 2021-11-05 +#+FILETAGS: :emacs: + +This section is primarily about development in a literate way, especially focused on [[http://howardism.org/Technical/Emacs/literate-devops.html][literate devops]]. + +#+BEGIN_SRC emacs-lisp :exports none + ;;; ha-org-babel.el --- -*- lexical-binding: t; -*- + ;; + ;; Copyright (C) 2021 Howard X. Abrams + ;; + ;; Author: Howard X. Abrams + ;; Maintainer: Howard X. Abrams + ;; Created: November 5, 2021 + ;; + ;; This file is not part of GNU Emacs. + ;; + ;; *NB:* Do not edit this file. Instead, edit the original literate file at: + ;; /Users/howard.abrams/other/hamacs/ha-org-babel.org + ;; And tangle the file to recreate this one. + ;; + ;;; Code: + #+END_SRC + +* Introduction +Let's turn on all the languages: +#+BEGIN_SRC emacs-lisp + (use-package org + :init + (setq org-confirm-babel-evaluate nil + org-src-fontify-natively t + org-src-tab-acts-natively t) + :config + (add-to-list 'org-src-lang-modes '("dot" . "graphviz-dot")) + + (org-babel-do-load-languages 'org-babel-load-languages + '((shell . t) + (js . t) + (emacs-lisp . t) + (clojure . t) + (python . t) + (ruby . t) + (dot . t) + (css . t) + (plantuml . t)))) +#+END_SRC + +#+RESULTS: +: t + + + +* Technical Artifacts :noexport: + +Let's provide a name so that the file can be required: + +#+BEGIN_SRC emacs-lisp :exports none + (provide 'ha-org-babel) + ;;; ha-org-babel.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: + +#+PROPERTY: header-args:sh :tangle no +#+PROPERTY: header-args:emacs-lisp :tangle 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 + +# Local Variables: +# eval: (add-hook 'after-save-hook #'org-babel-tangle t t) +# End: diff --git a/ha-org-clipboard.org b/ha-org-clipboard.org new file mode 100644 index 0000000..80814b6 --- /dev/null +++ b/ha-org-clipboard.org @@ -0,0 +1,190 @@ +#+TITLE: Pasting the Org Clipboard +#+AUTHOR: Howard X. Abrams +#+EMAIL: howard.abrams@gmail.com +#+DATE: 2020-09-15 +#+FILETAGS: :emacs: + +A literate programming file of functions for formatting the clipboard. +# *Note:* After each change, /tangle it/ to the source destination with ~C-c C-v t~. + +#+BEGIN_SRC emacs-lisp :exports none +;;; org-clipboard.el --- A literate programming file of functions for formatting the clipboard. -*- lexical-binding: t; -*- +;; +;; Copyright (C) 2020 Howard X. Abrams +;; +;; Author: Howard X. Abrams +;; Maintainer: Howard X. Abrams +;; Created: September 15, 2020 +;; +;; This file is not part of GNU Emacs. +;; +;; *NB:* Do not edit this file. Instead, edit the original literate file at: +;; /home/howard/other/hamacs/org-clipboard.org +;; And tangle the file to recreate this one. +;; +;;; Code: +#+END_SRC +* Introduction +I would like to paste the formatted contents of the clipboard into an Org file /as org-formatted text/. +* The Clipboard + +Functions to help convert content from the operating system's clipboard into org-mode-compatible text. + +Each operating system as a different way of working with the clipboard, so let's create an operating-system abstraction: + +#+BEGIN_SRC emacs-lisp +(defun ha/get-clipboard () + "Returns a list where the first entry is the content type, +either :html or :text, and the second is the clipboard contents." + (if (eq system-type 'darwin) + (ha/get-mac-clipboard) + (ha/get-linux-clipboard))) +#+END_SRC + +Let's define the clipboard for a Mac. The challenge here is that we need to binary unpack the data from a call to Applescript. + +#+BEGIN_SRC emacs-lisp +(defun ha/get-mac-clipboard () + "Returns a list where the first entry is the content type, +either :html or :text, and the second is the clipboard contents." + (destructuring-bind (exit-code contents) + (shell-command-with-exit-code "osascript" "-e" "the clipboard as \"HTML\"") + (if (= 0 exit-code) + (list :html (ha/convert-applescript-to-html contents)) + (list :text (shell-command-to-string "osascript -e 'the clipboard'"))))) + +(defun ha/convert-applescript-to-html (packed-contents) + "Applescript's clipboard returns the contents in a packed array. +Convert and return this encoding into a UTF-8 string." + (cl-flet ((hex-pack-bytes (tuple) (string-to-number (apply 'string tuple) 16))) + (let* ((data (-> packed-contents + (substring 10 -2) ; strips off the =«data RTF= and =»\= bits + (string-to-list))) + (byte-seq (->> data + (-partition 2) ; group each two hex characters into tuple + (mapcar #'hex-pack-bytes)))) + (decode-coding-string + (mapconcat #'byte-to-string byte-seq "") 'utf-8)))) +#+END_SRC + +And define the same interface for Linux. Keep in mind, we need the exit code from calling a process, so I am going to define/use a helper function (that really should go into the piper project). + +#+BEGIN_SRC emacs-lisp +(defun ha/get-linux-clipboard () + "Return the clipbaard for a Unix-based system. See `ha/get-clipboard'." + (destructuring-bind (exit-code contents) + (shell-command-with-exit-code "xclip" "-o" "-t" "text/html") + (if (= 0 exit-code) + (list :html contents) + (list :text (shell-command-to-string "xclip -o"))))) + +(defun shell-command-with-exit-code (program &rest args) + "Run PROGRAM with ARGS and return the exit code and output in a list." + (with-temp-buffer + (list (apply 'call-process program nil (current-buffer) nil args) + (buffer-string)))) +#+END_SRC + +* Converting from Slack + +We can assume that most non-HTML text could be Slack-like: + +#+BEGIN_SRC emacs-lisp +(defun ha/slack-to-markdown-buffer () + "Odd function that converts Slack’s version of Markdown (where +code is delimited with triple backticks) into a more formal +four-space indent markdown style." + (goto-char (point-min)) + ;; Begin by converting all Carriage Returns to line feeds: + (while (re-search-forward " " nil t) + (replace-match " +")) + + (goto-char (point-min)) + (while (re-search-forward "```" nil t) + (replace-match " + + ") + (let ((starting-bounds (point))) + (if (re-search-forward "```[ \t]*" nil t) + (let ((ending-bounds (point))) + (replace-match " + +") + (goto-char starting-bounds) + (while (< (point) ending-bounds) + (next-line) + (beginning-of-line) + (insert " "))))))) +#+END_SRC + +* Converting to Org + +Let's work top-down at this point with the interactive function that inserts the clipboard into the current buffer: + +#+BEGIN_SRC emacs-lisp +(defun ha/org-yank-clipboard () + "Yanks (pastes) the contents of the Apple Mac clipboard in an +org-mode-compatible format." + (interactive) + (insert (ha/org-clipboard))) +#+END_SRC + +The heavy lifting, however is done by this function. Note that I will need another function to tidy up the output from =pandoc= that will be more to my liking. + +#+BEGIN_SRC emacs-lisp +(defun ha/org-clipboard () + "Return the contents of the clipboard in org-mode format." + (seq-let (type contents) (ha/get-clipboard) + (with-temp-buffer + (insert contents) + (if (eq :html type) + (shell-command-on-region (point-min) (point-max) "pandoc -f html -t org" t t) + (ha/slack-to-markdown-buffer) + (shell-command-on-region (point-min) (point-max) "pandoc -f markdown -t org" t t)) + (ha/html-paste-touchup) + (buffer-substring-no-properties (point-min) (point-max))))) + +(defun ha/html-paste-touchup () + "Attempts to fix the org produced by `pandoc'' that seems to plague us." + (interactive) + (dolist (combo '((" (edited) " " ") ; Slack appends this phrase that is never needed + (" " " ") ; Pandoc's fixed space needs to go + ("\\\\\\\\$" "") ; Pandoc's fixed space needs to go + ("\\[\\[https://slack-imgs\\.com/.*\\.png\\]\\]" "") ;; Emoticons associated with a user + ("\\[\\[https://.*\\.slack\\.com/archives.*\\]\\[\\(.*\n.*\\)\\]\\]" "") + ("\\[\\[https://app\.slack\.com/team.*\\]\\[\\(.*\\)\n\\(.*\\)\\]\\]" " - *\\1 \\2:* ") + ("\\[\\[https://app\.slack\.com/team.*\\]\\[\\(.*\n.*\\)\\]\\]" " - *\\1:* ") + ("^- \\(.*\\)\\n " "- \\1 ") + ("^ *<<[0-9\.]+>>\n\n" ""))) ;; Slack includes these time things? + (seq-let (search replace) combo + (goto-char (point-min)) + (while (re-search-forward search nil t) + (replace-match replace))))) +#+END_SRC + +* Keybinding to Paste into Org Files +We +#+BEGIN_SRC emacs-lisp +(general-evil-define-key 'normal org-mode-map + :prefix "SPC m" + "y" 'ha/org-yank-clipboard) +#+END_SRC + +* Technical Artifacts :noexport: +Let's provide a name so we can =require= this file: + +#+BEGIN_SRC emacs-lisp +(provide 'ha-org-clipboard) +;;; ha-org-clipboard.el ends here +#+END_SRC + +#+DESCRIPTION: A literate programming version of functions for formatting the clipboard. + +#+PROPERTY: header-args:sh :tangle no +#+PROPERTY: header-args:emacs-lisp :tangle 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 diff --git a/ha-org-sprint.org b/ha-org-sprint.org new file mode 100644 index 0000000..357f755 --- /dev/null +++ b/ha-org-sprint.org @@ -0,0 +1,434 @@ +#+TITLE: My Sprint Calculations and Support +#+AUTHOR: Howard X. Abrams +#+EMAIL: howard.abrams@gmail.com +#+DATE: 2020-09-25 +#+FILETAGS: :emacs: + +A literate program for configuring org files for work-related notes. +# *Note:* After each change, /tangle it/ to the source destination with ~C-c C-v t~. + +#+BEGIN_SRC emacs-lisp :exports none +;;; org-sp rint.el --- A literate program for configuring org files for work-related notes. -*- lexical-binding: t; -*- +;; +;; Copyright (C) 2020 Howard X. Abrams +;; +;; Author: Howard X. Abrams +;; 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: +;; /Users/howard.abrams/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)) + '<>) + "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 (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 (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) + (find-file (sprint-current-file date))) +#+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 diff --git a/ha-org.org b/ha-org.org index d5ce021..10124d2 100644 --- a/ha-org.org +++ b/ha-org.org @@ -241,10 +241,24 @@ Of course, I need an 'undo' feature when the meeting is over... Whenever I edit Emacs Lisp blocks from my tangle-able configuration files, I get a lot of superfluous warnings. Let's turn them off. #+BEGIN_SRC emacs-lisp -(defun disable-fylcheck-in-org-src-block () +(defun disable-flycheck-in-org-src-block () (setq-local flycheck-disabled-checkers '(emacs-lisp-checkdoc))) -(add-hook 'org-src-mode-hook 'disable-fylcheck-in-org-src-block) +(add-hook 'org-src-mode-hook 'disable-flycheck-in-org-src-block) +#+END_SRC +And turn on ALL the languages: +#+BEGIN_SRC emacs-lisp + (org-babel-do-load-languages 'org-babel-load-languages + '((shell . t) + (js . t) + (emacs-lisp . t) + (clojure . t) + (python . t) + (ruby . t) + (dot . t) + (css . t) + (plantuml . t))) + #+END_SRC *** Next Image When I create images or other artifacts that I consider /part/ of the org document, I want to have them based on the org file, but with a prepended number. Keeping track of what numbers are now free is difficult, so for a /default/ let's figure it out: