Commit tangled versions of my website code

This makes this project self-contained for others to steal.
This commit is contained in:
Howard Abrams 2021-12-29 09:36:58 -08:00
parent 2dcac6e761
commit 2380d70508
4 changed files with 622 additions and 0 deletions

107
elisp/beep.el Normal file
View file

@ -0,0 +1,107 @@
;;; beep.el --- A literate programming for alerting after long projects. -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2020 Howard X. Abrams
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
;; Maintainer: Howard X. Abrams <howard.abrams@gmail.com>
;; Created: December 23, 2020
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; ~/website/Technical/Emacs/beep-for-emacs.org
;; And tangle the file to recreate this one.
;;
;;; Code:
(defvar beep-alert-sound-file
(expand-file-name "~/other/hamacs/beep-notify.wav")
"A WAV or AU file used at the completion of a function.")
;; My replacement in case we can't play internal sounds:
(defun beep--beep ()
"Play a default notification sound file.
Customize the variable, `beep-alert-sound-file' to adjust the sound."
(if (fboundp 'play-sound-internal)
(play-sound-file beep-alert-sound-file)
(call-process-shell-command (format "afplay %s &" beep-alert-sound-file) nil 0)))
(defvar beep-speech-executable "say %s"
"An OS-dependent shell string to speak. Replaces `%s' with a phrase.")
(defun beep--speak (phrase)
"Call a program to speak the string, PHRASE.
Customize the variable, `beep-speech-executable'."
(let ((command (format beep-speech-executable phrase)))
(shell-command command)))
(defun beep--when-finished (phrase)
"Notify us with string, PHRASE, to grab our attention.
Useful after a long process has completed, but use sparingly,
as this can be pretty distracting."
(message phrase)
(when (functionp 'alert)
(alert phrase :title "Completed"))
(beep--beep)
(beep--speak phrase))
(defun compile-and-notify ()
"Call `counsel-compile' and notify us when finished.
See `beep--when-finished' for details."
(interactive)
(let ((root (projectile-project-root)))
(counsel-compile root)
(beep--when-finished "The compile command has finished.")))
(defvar beep-func-too-long-time 5
"The number of seconds a function runs before it is considered taking too much time, and needing to be alerted when it has finished.")
(defun beep--after-function (func)
"Call the function, FUNC, interactively, and notify us when completed."
(let ((start-time (current-time))
duration)
(call-interactively func)
(setq duration (thread-first
(current-time)
(time-subtract start-time)
decode-time
first))
(when (> duration beep-func-too-long-time)
(beep--when-finished (format "The function, %s, has finished." func)))))
(defun recompile-and-notify ()
"Call `recompile' and notify us when finished.
See `beep--when-finished' for details."
(interactive)
(beep--after-function 'recompile))
(global-set-key (kbd "C-c c") 'recompile-and-notify)
(global-set-key (kbd "C-c C") 'compile-and-notify)
(defun beep-when-runs-too-long (orig-function &rest args)
"Notifies us about the completion of ORIG-FUNCTION.
Useful as after advice to long-running functions, for instance:
(advice-add 'org-publish :around #'beep-when-runs-too-long)"
(let ((start-time (current-time))
duration)
(apply orig-function args)
(setq duration (thread-first
(current-time)
(time-subtract start-time)
decode-time
first))
(when (> duration beep-func-too-long-time)
(beep--when-finished (format "The function, %s, has finished."
(beep--extract-function-name orig-function))))))
(defun beep--extract-function-name (expr)
"Extracts the original function from a lambda expression, EXPR."
(if (listp expr)
(if (equal (car expr) 'lambda)
(car (cadr expr))
expr)
expr))
(provide 'beep)
;;; beep.el ends here

203
elisp/boxes-extras.el Normal file
View file

@ -0,0 +1,203 @@
;;; BOXES-EXTRAS --- Refiling Subtrees to Proper Org Files
;;
;; Author: Howard Abrams <howard@howardabrams.com>
;; Copyright © 2019, Howard Abrams, all rights reserved.
;; Created: 7 January 2019
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Commentary:
;;
;; WARNING: This file is tangled from its original essay.
;;
;; For a thorough explanation of this code, see the online essay:
;; http://www.howardism.org/Technical/Emacs/getting-even-more-boxes-done.html
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 3, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
;; Floor, Boston, MA 02110-1301, USA.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Code:
(defun org-subtree-metadata ()
"Return a list of key aspects of an org-subtree. Includes the
following: header text, body contents, list of tags, region list
of the start and end of the subtree."
(save-excursion
;; Jump to the parent header if not already on a header
(when (not (org-at-heading-p))
(org-previous-visible-heading 1))
(let* ((context (org-element-context))
(attrs (second context))
(props (org-entry-properties)))
(list :region (list (plist-get attrs :begin) (plist-get attrs :end))
:header (plist-get attrs :title)
:tags (org-get-subtree-tags props)
:properties (org-get-subtree-properties attrs)
:body (org-get-subtree-content attrs)))))
(defun org-get-subtree-tags (&optional props)
"Given the properties, PROPS, from a call to
`org-entry-properties', return a list of tags."
(unless props
(setq props (org-entry-properties)))
(let ((tag-label (if org-get-subtree-tags-inherited "ALLTAGS" "TAGS")))
(-some->> props
(assoc tag-label)
cdr
substring-no-properties
(s-split ":")
(--filter (not (equalp "" it))))))
(defvar org-get-subtree-tags-inherited t
"Returns a subtree's tags, and all tags inherited (from tags
specified in parents headlines or on the file itself). Defaults
to true.")
(defun org-get-subtree-properties (attributes)
"Return a list of tuples of a subtrees properties where the keys are strings."
(defun symbol-upcase? (sym)
(let ((case-fold-search nil))
(string-match-p "^:[A-Z]+$" (symbol-name sym))))
(defun convert-tuple (tup)
(let ((key (first tup))
(val (second tup)))
(list (substring (symbol-name key) 1) val)))
(->> attributes
(-partition 2) ; Convert plist to list of tuples
(--filter (symbol-upcase? (first it))) ; Remove lowercase tuples
(-map 'convert-tuple)))
(defun org-get-subtree-content (attributes)
"Return the contents of the current subtree as a string."
(let ((header-components '(clock diary-sexp drawer headline inlinetask
node-property planning property-drawer section)))
(goto-char (plist-get attributes :contents-begin))
;; Walk down past the properties, etc.
(while
(let* ((cntx (org-element-context))
(elem (first cntx))
(props (second cntx)))
(when (member elem header-components)
(goto-char (plist-get props :end)))))
;; At this point, we are at the beginning of what we consider
;; the contents of the subtree, so we can return part of the buffer:
(buffer-substring-no-properties (point) (org-end-of-subtree))))
(defun org-refile-subtree-to-file (dir)
"Archive the org-mode subtree and create an entry in the
directory folder specified by DIR. It attempts to move as many of
the subtree's properties and other features to the new file."
(interactive "DDestination: ")
(let* ((props (org-subtree-metadata))
(head (plist-get props :header))
(body (plist-get props :body))
(tags (plist-get props :tags))
(properties (plist-get props :properties))
(area (plist-get props :region))
(filename (org-filename-from-title head))
(filepath (format "%s/%s.org" dir filename)))
(apply #'delete-region area)
(org-create-org-file filepath head body tags properties)))
(defun org-create-org-file (filepath header body tags properties)
"Create a new Org file by FILEPATH. The contents of the file is
pre-populated with the HEADER, BODY and any associated TAGS."
(find-file-other-window filepath)
(org-set-file-property "TITLE" header t)
(when tags
(org-set-file-property "FILETAGS" (s-join " " tags)))
;; Insert any drawer properties as #+PROPERTY entries:
(when properties
(goto-char (point-min))
(or (re-search-forward "^\s*$" nil t) (point-max))
(--map (insert (format "#+PROPERTY: %s %s" (first it) (second it))) properties))
;; My auto-insert often adds an initial headline for a subtree, and in this
;; case, I don't want that... Yeah, this isn't really globally applicable,
;; but it shouldn't cause a problem for others.
(when (re-search-forward "^\\* [0-9]$" nil t)
(replace-match ""))
(delete-blank-lines)
(goto-char (point-max))
(insert "\n")
(insert body))
(defun org-filename-from-title (title)
"Creates a useful filename based on a header string, TITLE.
For instance, given the string: What's all this then?
This function will return: whats-all-this-then"
(let* ((no-letters (rx (one-or-more (not alphanumeric))))
(init-try (->> title
downcase
(replace-regexp-in-string "'" "")
(replace-regexp-in-string no-letters "-"))))
(string-trim init-try "-+" "-+")))
(defun org-set-file-property (key value &optional spot)
"Make sure file contains a top-level, file-wide property.
KEY is something like `TITLE' or `FILETAGS'. This function makes
sure that the property contains the contents of VALUE, and if the
file doesn't have the property, it is inserted at either SPOT, or
if nil,the top of the file."
(save-excursion
(goto-char (point-min))
(let ((case-fold-search t))
(if (re-search-forward (format "^#\\+%s:\s*\\(.*\\)" key) nil t)
(replace-match value nil nil nil 1)
(cond
;; if SPOT is a number, go to it:
((numberp spot) (goto-char spot))
;; If SPOT is not given, jump to first blank line:
((null spot) (progn (goto-char (point-min))
(re-search-forward "^\s*$" nil t)))
(t (goto-char (point-min))))
(insert (format "#+%s: %s\n" (upcase key) value))))))
(defun org-refile-to-projects-dir ()
"Move the current subtree to a file in the `projects' directory."
(interactive)
(org-refile-subtree-to-file org-default-projects-dir))
(defun org-refile-to-technical-dir ()
"Move the current subtree to a file in the `technical' directory."
(interactive)
(org-refile-subtree-to-file org-default-technical-dir))
(define-auto-insert "/personal/*\\.org" ["personal.org" ha/autoinsert-yas-expand])
(defun org-refile-to-personal-dir ()
"Move the current subtree to a file in the `personal' directory."
(interactive)
(org-refile-subtree-to-file org-default-personal-dir))
(provide 'boxes-extras)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; boxes-extras.el ends here

238
elisp/boxes.el Normal file
View file

@ -0,0 +1,238 @@
;;; BOXES --- An opinionated approach to a GTD workflow
;;
;; Author: Howard Abrams <howard@howardabrams.com>
;; Copyright © 2019, Howard Abrams, all rights reserved.
;; Created: 7 January 2019
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Commentary:
;;
;; WARNING: This file is tangled from its original essay.
;;
;; For a thorough explanation of this code, see the online essay:
;; http://www.howardism.org/Technical/Emacs/getting-more-boxes-done.html
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 3, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
;; Floor, Boston, MA 02110-1301, USA.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Code:
(defvar org-default-projects-dir "~/projects" "Primary GTD directory")
(defvar org-default-technical-dir "~/technical" "Directory of shareable notes")
(defvar org-default-personal-dir "~/personal" "Directory of un-shareable, personal notes")
(defvar org-default-completed-dir "~/projects/trophies" "Directory of completed project files")
(defvar org-default-inbox-file "~/projects/breathe.org" "New stuff collects in this file")
(defvar org-default-tasks-file "~/projects/tasks.org" "Tasks, TODOs and little projects")
(defvar org-default-incubate-file "~/projects/incubate.org" "Ideas simmering on back burner")
(defvar org-default-completed-file nil "Ideas simmering on back burner")
(defvar org-default-notes-file "~/personal/general-notes.org" "Non-actionable, personal notes")
(defvar org-default-media-file "~/projects/media.org" "White papers and links to other things to check out")
(defvar org-capture-templates (list))
(add-to-list 'org-capture-templates
`("t" "Task Entry" entry
(file ,org-default-inbox-file)
"* %?\n:PROPERTIES:\n:CREATED:%U\n:END:\n\n%i\n\nFrom: %a"
:empty-lines 1))
(defhydra hydra-org-refiler (org-mode-map "C-c s" :hint nil)
"
^Navigate^ ^Refile^ ^Move^ ^Update^ ^Go To^ ^Dired^
^^^^^^^^^^---------------------------------------------------------------------------------------
_k_: previous _t_: tasks _m X_: projects _T_: todo task _g t_: tasks _g X_: projects
_j_: next _i_/_I_: incubate _m P_: personal _S_: schedule _g i_: incubate _g P_: personal
_c_: archive _p_: personal _m T_: technical _D_: deadline _g x_: inbox _g T_: technical
_d_: delete _r_: refile _R_: rename _g n_: notes _g C_: completed
"
("<up>" org-previous-visible-heading)
("<down>" org-next-visible-heading)
("k" org-previous-visible-heading)
("j" org-next-visible-heading)
("c" org-archive-subtree-as-completed)
("d" org-cut-subtree)
("t" org-refile-to-task)
("i" org-refile-to-incubate)
("I" org-refile-to-another-incubator)
("p" org-refile-to-personal-notes)
("r" org-refile)
("m X" org-refile-to-projects-dir)
("m P" org-refile-to-personal-dir)
("m T" org-refile-to-technical-dir)
("T" org-todo)
("S" org-schedule)
("D" org-deadline)
("R" org-rename-header)
("g t" (find-file-other-window org-default-tasks-file))
("g i" (find-file-other-window org-default-incubate-file))
("g x" (find-file-other-window org-default-inbox-file))
("g c" (find-file-other-window org-default-completed-file))
("g n" (find-file-other-window org-default-notes-file))
("g X" (dired org-default-projects-dir))
("g P" (dired org-default-personal-dir))
("g T" (dired org-default-technical-dir))
("g C" (dired org-default-completed-dir))
("[\t]" (org-cycle))
("s" (org-save-all-org-buffers) "save")
("<tab>" (org-cycle) "toggle")
("q" nil "quit"))
(setq org-refile-use-outline-path 'file
org-refile-allow-creating-parent-nodes t
org-outline-path-complete-in-steps nil)
(setq org-refile-targets
(append `((,(expand-file-name org-default-media-file) :level . 1)
(,(expand-file-name org-default-notes-file) :level . 0))
(->>
(directory-files org-default-projects-dir nil ".org")
(-remove-item (file-name-base org-default-media-file))
(--remove (s-starts-with? "." it))
(--remove (s-ends-with? "_archive" it))
(--map (format "%s/%s" (expand-file-name org-default-projects-dir) it))
(--map `(,it :level . 0)))))
(setq org-refile-target-table nil)
(defun org-subtree-region ()
"Return a list of the start and end of a subtree."
(save-excursion
(list (progn (org-back-to-heading) (point))
(progn (org-end-of-subtree) (point)))))
(defun org-refile-directly (file-dest)
"Move the current subtree to the end of FILE-DEST.
If SHOW-AFTER is non-nil, show the destination window,
otherwise, this destination buffer is not shown."
(interactive "fDestination: ")
(defun dump-it (file contents)
(find-file-other-window file-dest)
(goto-char (point-max))
(insert "\n" contents))
(save-excursion
(let* ((region (org-subtree-region))
(contents (buffer-substring (first region) (second region))))
(apply 'kill-region region)
(if org-refile-directly-show-after
(save-current-buffer (dump-it file-dest contents))
(save-window-excursion (dump-it file-dest contents))))))
(defvar org-refile-directly-show-after nil
"When refiling directly (using the `org-refile-directly'
function), show the destination buffer afterwards if this is set
to `t', otherwise, just do everything in the background.")
(defun org-refile-to-incubate ()
"Refile (move) the current Org subtree to `org-default-incubate-fire'."
(interactive)
(org-refile-directly org-default-incubate-file))
(defun org-refile-to-task ()
"Refile (move) the current Org subtree to `org-default-tasks-file'."
(interactive)
(org-refile-directly org-default-tasks-file))
(defun org-refile-to-personal-notes ()
"Refile (move) the current Org subtree to `org-default-notes-file'."
(interactive)
(org-refile-directly org-default-notes-file))
(defun org-refile-to-completed ()
"Refile (move) the current Org subtree to `org-default-completed-file',
unless it doesn't exist, in which case, refile to today's journal entry."
(interactive)
(if (and org-default-completed-file (file-exists-p org-default-completed-file))
(org-refile-directly org-default-completed-file)
(org-refile-directly (get-journal-file-today))))
(defun org-rename-header (label)
"Rename the current section's header to LABEL, and moves the
point to the end of the line."
(interactive (list
(read-string "Header: "
(substring-no-properties (org-get-heading t t t t)))))
(org-back-to-heading)
(replace-string (org-get-heading t t t t) label))
(defun org-archive-subtree-as-completed ()
"Archives the current subtree to today's current journal entry."
(interactive)
;; According to the docs for `org-archive-subtree', the state should be
;; automatically marked as DONE, but I don't notice it, so let's force:
(when (not (equal "DONE" (org-get-todo-state)))
(org-todo "DONE"))
(let* ((org-archive-file (or org-default-completed-file
(todays-journal-entry)))
(org-archive-location (format "%s::" org-archive-file)))
(org-archive-subtree)))
(defun todays-journal-entry ()
"Return the full pathname to the day's journal entry file.
Granted, this assumes each journal's file entry to be formatted
with year/month/day, as in `20190104' for January 4th.
Note: `org-journal-dir' variable must be set to the directory
where all good journal entries live, e.g. ~/journal."
(let* ((daily-name (format-time-string "%Y%m%d"))
(file-name (concat org-journal-dir daily-name)))
(expand-file-name file-name)))
;; Attempt to load the extra library functions tangled from a different essay:
(condition-case nil
(load-library "boxes-extras")
(error
(defun org-refile-to-projects-dir ()
(interactive)
(message "Need to load the 'boxes-extra project first."))
(defun org-refile-to-personal-dir ()
(interactive)
(message "Need to load the 'boxes-extra project first."))
(defun org-refile-to-technical-dir ()
(interactive)
(message "Need to load the 'boxes-extra project first."))))
(defun org-boxes-workflow ()
"Load the default tasks file and start our hydra on the first task shown."
(interactive)
(let ((org-startup-folded nil))
(find-file org-default-inbox-file)
(delete-other-windows)
(ignore-errors
(ha/org-agenda))
(delete-other-windows)
(split-window-right-and-focus)
(pop-to-buffer (get-file-buffer org-default-inbox-file))
(goto-char (point-min))
(org-next-visible-heading 1)
(hydra-org-refiler/body)))
(defun ha/org-agenda ()
"Displays my favorite agenda perspective."
(interactive)
(org-agenda nil "a")
(get-buffer "*Org Agenda*")
(execute-kbd-macro (kbd "A t")))
(provide 'boxes)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; boxes.el ends here

74
elisp/ha-focus.el Normal file
View file

@ -0,0 +1,74 @@
;;; ha-focus.el --- Emacs lisp code to focus my thoughts. -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2021 Howard X. Abrams
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
;; Maintainer: Howard X. Abrams <howard.abrams@workday.com>
;; Created: May 28, 2021
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; ~/website/Technical/Emacs/focused-work.org
;; And tangle the file to recreate this one.
;;
;;; Code:
(defvar ha-focus-timer nil "A timer reference for the ha-focus functions")
(defun ha-focus-countdown-timer (minutes fun)
(let ((the-future (* minutes 60)))
(run-at-time the-future nil fun)))
(defun ha-focus-begin ()
"Start a concerted, focused effort, ala Pomodoro Technique.
We first clock into the current org-mode header (or last one),
start some music to indicate we are working, and set a timer.
Call `ha-focus-break' when finished."
(interactive)
(ha-focus-countdown-timer 25 'ha-focus-break)
(ha-focus--command "tell application \"Spotify\" to play")
(if (eq major-mode 'org-mode)
(org-clock-in)
(org-clock-in-last)))
(defun ha-focus-break ()
"Stop the focused time by stopping the music.
This also starts another break timer, that calls
`ha-focus-break-over' when finished."
(interactive)
(run-with-idle-timer 5 nil 'ha-focus-capture)
(ha-focus--command "tell application \"Spotify\" to pause")
(message "Time to take a break."))
(defun ha-focus-capture ()
(ignore-errors
(org-capture nil "cc")
(sit-for 1)
(org-clock-out))
(ha-focus-countdown-timer 5 'ha-focus-break-over)
(message "Taking a much needed break..."))
(defun ha-focus-break-over ()
"Message me to know that the break time is over. Notice that
this doesn't start anything automatically, as I may have simply
wandered off."
(ha-focus--command "set v to output volume of (get volume settings)
set volume output volume 1
say \"Break time over. Back on your head.\"
set volume output volume v"))
(defun ha-focus--command (osascript)
"Runs OSASCRIPT by passing to the `osascript' command asynchronously."
(async-start-process "focus-os" "osascript" 'ha-focus--command-callback "-e" osascript))
(defun ha-focus--command-callback (proc)
"Asynchronously called when the `osascript' process finishes."
(message "Finished calling osascript."))
(global-set-key (kbd "<f15>") 'ha-focus-begin)
(global-set-key (kbd "S-<f15>") 'ha-focus-break)
(provide 'ha-focus)
;;; ha-focus.el ends here