aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-02-21 13:53:14 -0600
committerGitHub <[email protected]>2026-02-21 13:53:14 -0600
commit2601873a0da273769420e48d23221ebf8b600fe2 (patch)
tree70e204c47539d46cb1ca2ac4bab71b57d815f58e
parente136d6671654561ac24fa98792442829339a639e (diff)
downloadcleberg.net-2601873a0da273769420e48d23221ebf8b600fe2.tar.gz
cleberg.net-2601873a0da273769420e48d23221ebf8b600fe2.tar.bz2
cleberg.net-2601873a0da273769420e48d23221ebf8b600fe2.zip
add /garden section to site (#5)
* publish new post: automating-weblorg-deployments * fix publish.el comments * allow for case-insensitive env variable * obfuscate port * update run rules * update run rules * allow for case-insensitive env variable * publish new post: automating-weblorg-deployments * add /garden section to site
-rw-r--r--content/garden/org-mode.org133
-rw-r--r--publish.el17
-rw-r--r--theme/static/styles.css2
-rw-r--r--theme/templates/base.html1
-rw-r--r--theme/templates/garden.html18
5 files changed, 171 insertions, 0 deletions
diff --git a/content/garden/org-mode.org b/content/garden/org-mode.org
new file mode 100644
index 0000000..da6e087
--- /dev/null
+++ b/content/garden/org-mode.org
@@ -0,0 +1,133 @@
+#+title: org-mode
+#+slug: org-mode
+#+description: Tidbits about the best plaintext language: org-mode.
+#+date: [2026-02-21 Sat 12:39:27]
+
+* Time Reporting
+
+If you maintain a log of time clocks, you can create a weekly report that will
+break down time per heading, per day, per week.
+
+The time tracker file:
+
+#+begin_src org
+,#+title: Time Tracker
+,#+startup: showall
+
+# To generate a time report:
+# 1. Open Emacs with this file
+# 2. SPC z t (or M-x tr/time-report-interactive)
+# 3. Enter this file as input, a path for output, and optional date range (YYYY-MM-DD)
+
+,* Project 1
+:LOGBOOK:
+CLOCK: [2026-02-19 Thu 09:00]--[2026-02-19 Thu 12:00] => 3:00
+:END:
+,* Project 2
+:LOGBOOK:
+CLOCK: [2026-02-20 Fri 10:00]--[2026-02-20 Fri 16:00] => 6:00
+:END:
+#+end_src
+
+The elisp function to generate the report:
+
+#+begin_src elisp
+;; Time Report
+(defun tr/parse-org-clock-entries (file)
+ "Return list of (date heading minutes) from FILE's CLOCK entries."
+ (let (entries)
+ (with-current-buffer (find-file-noselect file)
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (while (re-search-forward
+ "CLOCK: \\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\) [A-Za-z]\\{3\\} [0-9:]\\{5\\}\\]--\\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\) [A-Za-z]\\{3\\} \\([0-9:]\\{5\\}\\)\\] => *\\([0-9]+\\):\\([0-9]+\\)"
+ nil t)
+ (let* ((date (match-string 1))
+ (h (string-to-number (match-string 4)))
+ (m (string-to-number (match-string 5)))
+ (mins (+ (* h 60) m))
+ (heading (save-excursion
+ (org-back-to-heading t)
+ (org-get-heading t t t t))))
+ (push (list date heading mins) entries)))))
+ (nreverse entries)))
+
+(defun tr/format-minutes (mins)
+ (format "%d:%02d" (/ mins 60) (mod mins 60)))
+
+(defun tr/generate-report (input-file output-file &optional tstart tend)
+ "Parse INPUT-FILE and write time report to OUTPUT-FILE."
+ (let* ((entries (tr/parse-org-clock-entries input-file))
+ (entries (if (or tstart tend)
+ (cl-remove-if-not
+ (lambda (e)
+ (let ((d (car e)))
+ (and (or (null tstart) (not (string< d tstart)))
+ (or (null tend) (not (string> d tend))))))
+ entries)
+ entries))
+ (totals (make-hash-table :test #'equal)))
+ (dolist (e entries)
+ (let ((key (cons (nth 0 e) (nth 1 e))))
+ (puthash key (+ (gethash key totals 0) (nth 2 e)) totals)))
+ (let* ((keys (hash-table-keys totals))
+ (keys (sort keys (lambda (a b)
+ (or (string< (car a) (car b))
+ (and (string= (car a) (car b))
+ (string< (cdr a) (cdr b)))))))
+ (current-week nil))
+ (with-temp-file output-file
+ (insert "#+title: Time Report\n\n")
+ (insert (format "| %-12s | %-45s | %5s |\n" "Date" "Code" "Hours"))
+ (insert (concat "|" (make-string 14 ?-) "+" (make-string 47 ?-) "+" (make-string 7 ?-) "|\n"))
+ (let (week-mins week-start)
+ (dolist (k keys)
+ (let* ((date (car k))
+ (heading (cdr k))
+ (mins (gethash k totals))
+ (time (date-to-time (concat date " 00:00:00")))
+ (week (format-time-string "%Y-W%V" time)))
+ (when (and week-start (not (string= week current-week)))
+ (insert (concat "|" (make-string 14 ?-) "+" (make-string 47 ?-) "+" (make-string 7 ?-) "|\n"))
+ (insert (format "| %-12s | %-45s | %5s |\n" current-week "WEEK TOTAL" (tr/format-minutes week-mins)))
+ (insert (concat "|" (make-string 14 ?-) "+" (make-string 47 ?-) "+" (make-string 7 ?-) "|\n"))
+ (setq week-mins 0))
+ (unless (string= week current-week)
+ (setq current-week week week-start date week-mins 0))
+ (setq week-mins (+ week-mins mins))
+ (insert (format "| %-12s | %-45s | %5s |\n" date heading (tr/format-minutes mins)))))
+ (when week-mins
+ (insert (concat "|" (make-string 14 ?-) "+" (make-string 47 ?-) "+" (make-string 7 ?-) "|\n"))
+ (insert (format "| %-12s | %-45s | %5s |\n" current-week "WEEK TOTAL" (tr/format-minutes week-mins)))))
+ (insert "\n")))))
+
+(defun tr/time-report-interactive ()
+ "Interactively generate a time report from an org file."
+ (interactive)
+ (let* ((input (read-file-name "Time tracking file: " nil nil t))
+ (output (read-file-name "Output report file: "))
+ (tstart (read-string "Start date (YYYY-MM-DD, blank for none): "))
+ (tend (read-string "End date (YYYY-MM-DD, blank for none): "))
+ (tstart (if (string-empty-p tstart) nil tstart))
+ (tend (if (string-empty-p tend) nil tend)))
+ (tr/generate-report input output tstart tend)
+ (find-file output)
+ (message "Report written to %s" output)))
+
+;; Map time report to keybindings
+(map! :leader
+ :desc "Generate time report" "z t" #'tr/time-report-interactive)
+#+end_src
+
+The output file:
+
+#+begin_src org
+,#+title: Time Report
+
+| Date | Code | Hours |
+|------------+------------+-------|
+| 2026-02-19 | Project 1 | 3:00 |
+| 2026-02-20 | Project 2 | 6:00 |
+|------------+------------+-------|
+| 2026-W08 | WEEK TOTAL | 9:00 |
+#+end_src
diff --git a/publish.el b/publish.el
index 7f2b4be..cd3de56 100644
--- a/publish.el
+++ b/publish.el
@@ -52,6 +52,23 @@
:output ".build/blog/index.html"
:url "/blog/")
+;; Garden post route
+(weblorg-route
+ :name "garden"
+ :input-pattern "content/garden/*.org"
+ :template "post.html"
+ :output ".build/garden/{{ slug }}.html"
+ :url "/garden/{{ slug }}.html")
+
+;; Garden index page route
+(weblorg-route
+ :name "garden-index"
+ :input-pattern "content/garden/*.org"
+ :input-aggregate #'weblorg-input-aggregate-all-desc
+ :template "garden.html"
+ :output ".build/garden/index.html"
+ :url "/garden/")
+
;; Page post route
(weblorg-route
:name "pages"
diff --git a/theme/static/styles.css b/theme/static/styles.css
index 7029e9e..37c3e4f 100644
--- a/theme/static/styles.css
+++ b/theme/static/styles.css
@@ -135,6 +135,8 @@ img:hover { filter: grayscale(0); }
gap: 0.5rem;
}
+/* -- Garden -- */
+
/* Style the footnote number to be a fixed width for alignment */
.footnum {
font-weight: bold;
diff --git a/theme/templates/base.html b/theme/templates/base.html
index 9f1c48e..3a2ff79 100644
--- a/theme/templates/base.html
+++ b/theme/templates/base.html
@@ -23,6 +23,7 @@
<li><a href="/">Home</a></li>
<li><a href="/about/">About</a></li>
<li><a href="/blog/">Blog</a></li>
+ <li><a href="/garden/">Garden</a></li>
<li><a href="/services/">Services</a></li>
</ul>
</nav>
diff --git a/theme/templates/garden.html b/theme/templates/garden.html
new file mode 100644
index 0000000..dda3884
--- /dev/null
+++ b/theme/templates/garden.html
@@ -0,0 +1,18 @@
+{% extends "base.html" %} {% block subtitle %}Garden | {% endblock %} {% block main %}
+<h1>Garden</h1>
+<p>
+ Notes in various states of growth. Some are stubs, some are developed.
+ Browse around.
+</p>
+<br>
+<ul class="garden-index">
+{% for post in posts %}
+<li>
+ <a href='{{ url_for("garden", slug=post.slug) }}'>{{ post.title }}</a>
+ {% if post.description %}
+ <span class="garden-desc">{{ post.description }}</span>
+ {% endif %}
+</li>
+{% endfor %}
+</ul>
+{% endblock %}