#+TITLE: Index * Hello There! Welcome to my website! I'm [[./about.org][akk0]]. I blog about Emacs, programming, meditation, otaku stuff, and other things that interest me. You can view the full list of pages on the [[./sitemap.org][sitemap]]. This site launched recently and is still under construction; please pardon the dust! I maintain a [[./projects/blog.org][somewhat up-to-date TO-DO list]]; don't hestitate to reach out to me through the email address in the footer for feedback or feature suggestions ^^". Here's this week's [[./journal/w43-2025.org][journal]]! ** /Pro Tips/ - You can fold and unfold sections by clicking the headline. Try it! - Click on any cell with a dotted outline in the habit tracker to see my journal entry for it. The currently selected cell will be highlighted in pink. ** Habits #+BEGIN_CENTER /Today is Saturday, 25th October./ #+END_CENTER #+BEGIN_SRC emacs-lisp :exports results :results value html :cache no (defun akk0/org-to-html (org-string) "Convert ORG-STRING to HTML." (with-temp-buffer (insert org-string) (org-mode) (org-export-as 'html nil nil t nil))) (defun akk0/sort-habits (habit-alist) "Sort habit-alist by a predefined order of custom-ids." (let ((order '("dailies-blogging" "dailies-meditation" "dailies-french" "dailies-engineering" "dailies-exercise" "dailies-drawing" "dailies-reading" "dailies-social"))) (sort (copy-sequence habit-alist) (lambda (a b) (let ((pos-a (or (cl-position (car a) order :test #'equal) 999)) (pos-b (or (cl-position (car b) order :test #'equal) 999))) (< pos-a pos-b)))))) (defun akk0/extract-habits (file) "Extract habits with date context from FILE. Returns a list of plists with :custom-id, :todo-state, :date, :day-of-year." (with-temp-buffer (insert-file-contents file) (org-mode) (let (results) (org-element-map (org-element-parse-buffer) 'headline (lambda (hl) (let ((custom-id (org-element-property :CUSTOM_ID hl)) (todo-state (org-element-property :todo-keyword hl))) (when (and custom-id todo-state) ;; Get parent properties for context (let* ((parent (org-element-property :parent hl)) (day-of-year (or (org-element-property :DAILIES-DAY parent) (let ((grandparent (org-element-property :parent parent))) (when grandparent (org-element-property :DAILIES-DAY grandparent))))) (body (org-element-interpret-data (org-element-contents hl)))) (push (list :custom-id custom-id :todo-state todo-state :day-of-year day-of-year :file file :body body) results)))))) (nreverse results)))) (defun akk0/extract-all-habits (files) (mapcan #'akk0/extract-habits files)) (setq akk0/journal-files (directory-files "~/Blog/org/journal/" t "^w.*\\.org$")) (defun akk0/habits-alist (habits) "Transform HABITS list into nested alists: custom-id → day-of-year → habit-data." (let (result) (dolist (habit habits) (let* ((custom-id (plist-get habit :custom-id)) (day-of-year (plist-get habit :day-of-year)) (todo-state (plist-get habit :todo-state)) (body (plist-get habit :body)) ;; Get the alist for this custom-id (inner-alist (alist-get custom-id result nil nil #'equal)) ;; Store full data instead of just todo-state (habit-data (list :todo-state todo-state :body body :day-of-year day-of-year)) ;; Update the inner alist (updated-inner (cons (cons day-of-year habit-data) inner-alist))) ;; Update result (setf (alist-get custom-id result nil nil #'equal) updated-inner))) result)) (setq habit-alist (akk0/habits-alist (akk0/extract-all-habits akk0/journal-files))) (defun akk0/get-habit-history (habit-alist custom-id day-number days-back window-size) (let* ((inner-alist (alist-get custom-id habit-alist nil nil #'equal)) (result nil) (all-states nil) (score-for-state (lambda (state) (cond ((equal state "NO") -1) ((equal state "YES") 1) ((equal state "EXCELLENT") 2) (t 0))))) (dotimes (i days-back) (let* ((current-day (+ (- day-number days-back) i 1)) (current-day-str (number-to-string current-day)) (habit-data (alist-get current-day-str inner-alist nil nil #'equal)) (todo-state (if habit-data (plist-get habit-data :todo-state) "NODATA")) (body (if habit-data (plist-get habit-data :body) "")) (doy (if habit-data (plist-get habit-data :day-of-year) "")) ) (push todo-state all-states) (let* ((window-states (seq-take all-states window-size)) (rolling-score (apply #'+ (mapcar score-for-state window-states)))) (push (list :todo-state todo-state :score (max 1 (min 5 (/ (+ rolling-score 5) 2))) :body body :doy doy) result)))) (nreverse result))) (defun akk0/habits-to-html-table (habit-alist day-number days-back window-size) "Generate HTML table of habits with rolling scores. Rows are custom-ids, columns are days." (let ((color-map '(("NODATA" . "grey") ("YES" . "green") ("NO" . "red") ("FREED" . "purple") ("EXCELLENT" . "blue"))) (symbol-map '(("NODATA" . "") ("YES" . "●") ("NO" . "×") ("FREED" . "♣") ("EXCELLENT" . "♦"))) (sorted-habits (akk0/sort-habits habit-alist)) (html "")) ;; Start table (setq html (concat html "\n")) ;; Header row with day numbers (setq html (concat html " \n \n")) (dotimes (i days-back) (let ((day (+ (- day-number days-back) i 1))) (setq html (concat html (cond ((= day day-number) "") ((= 0 (% (- day day-number) 7)) "") (t "\n")) ;; Data rows - one per habit (dolist (entry sorted-habits) (let* ((custom-id (car entry)) (history (akk0/get-habit-history habit-alist custom-id day-number days-back window-size))) (setq html (concat html (format " \n \n" (capitalize (string-remove-prefix "dailies-" custom-id))))) ;; Cell for each day (dolist (day-data history) (let* ((todo-state (plist-get day-data :todo-state)) (score (plist-get day-data :score)) (body (plist-get day-data :body)) (doy (plist-get day-data :doy)) (body-html (if (and body (not (string-empty-p body))) (akk0/org-to-html body) "")) (color (alist-get todo-state color-map nil nil #'equal)) (symbol (alist-get todo-state symbol-map nil nil #'equal)) (class (format "habit-brightness-%d" score)) (style-var (format "--%s%d" color score)) (escaped-body (replace-regexp-in-string "\"" """ (replace-regexp-in-string "\n" " " body-html))) ) (setq html (concat html (format " \n" class style-var escaped-body doy custom-id todo-state symbol))))) (setq html (concat html " \n")))) ;; End table (setq html (concat html "
")))))) (setq html (concat html "
%s%s
\n")) html)) (akk0/habits-to-html-table habit-alist 298 30 5) #+END_SRC #+BEGIN_EXPORT html Key: Unknown | × No | ● Yes | ♦ Excellent | ♣ Freed Up
This section intentionally left blank.
#+END_EXPORT