diff options
| author | Christian Cleberg <[email protected]> | 2026-02-21 13:53:14 -0600 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-02-21 13:53:14 -0600 |
| commit | 2601873a0da273769420e48d23221ebf8b600fe2 (patch) | |
| tree | 70e204c47539d46cb1ca2ac4bab71b57d815f58e | |
| parent | e136d6671654561ac24fa98792442829339a639e (diff) | |
| download | cleberg.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.org | 133 | ||||
| -rw-r--r-- | publish.el | 17 | ||||
| -rw-r--r-- | theme/static/styles.css | 2 | ||||
| -rw-r--r-- | theme/templates/base.html | 1 | ||||
| -rw-r--r-- | theme/templates/garden.html | 18 |
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 @@ -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 %} |
