;;; recently-updated.el --- Generate recently-updated.org -*- lexical-binding: t; -*- (require 'cl-lib) (require 'org) (defvar site-root (file-name-directory (or load-file-name buffer-file-name))) (defvar z-org-root (file-name-as-directory site-root) "Root directory of the Org source files.") ;; Which Org files should be skipped for Recently Updated (defun z/recent-omit-org-p (rel-org) "Return non-nil if REL-ORG (relative to `z-org-root`) should not be listed." (let* ((name (file-name-nondirectory rel-org))) (or ;; assets-ish / tags (string-prefix-p "assets/" rel-org) (string-match-p "/assets/" rel-org) (string-prefix-p "tags/" rel-org) (string-match-p "/tags/" rel-org) ;; generated/meta orgs (member name '("sitemap.org" "categories.org" "recently-updated.org" "wip.org" )) ;; any *-list.org sitemaps (posts-list.org, blogs-list.org, etc.) (string-match-p "-list\\.org\\'" name)))) (defun z/read-org-title+mtime (file) "Return (TITLE . MTIME) for Org FILE. TITLE from #+TITLE or file name. MTIME is the file's modification time (real 'last updated')." (with-temp-buffer (insert-file-contents file) (org-mode) (let* ((props (org-collect-keywords '("TITLE"))) (title-cell (assoc "TITLE" props)) (title (or (and title-cell (car (cdr title-cell))) (file-name-base file))) (mtime (nth 5 (file-attributes file)))) (cons title mtime)))) (defun z/read-org-title+date (file) "Return (TITLE . TIME) for Org FILE. TITLE: - from #+TITLE, or file-name if missing. TIME: - from #+DATE if parseable as an org time string, - otherwise from file's modification time." (with-temp-buffer (insert-file-contents file) (org-mode) (let* ((props (org-collect-keywords '("TITLE" "DATE"))) ;; TITLE (title-cell (assoc "TITLE" props)) (title (or (and title-cell (car (cdr title-cell))) (file-name-base file))) ;; DATE (date-cell (assoc "DATE" props)) (date-str (and date-cell (car (cdr date-cell)))) (time (or (and date-str (ignore-errors (org-time-string-to-time date-str))) (nth 5 (file-attributes file))))) (cons title time)))) (defun z/generate-recently-updated-org (&optional n) "Generate recently-updated.org in `z-org-root`. Lists top N most recently updated Org pages (excluding tags, sitemaps, *-list.org, etc). Defaults to N=30." (let* ((count (or n 30)) (org-files (directory-files-recursively z-org-root "\\.org\\'")) (items '())) ;; Collect (REL PATH, TITLE, TIME) (dolist (full org-files) (let ((rel (file-relative-name full z-org-root))) (unless (z/recent-omit-org-p rel) (pcase-let* ((`(,title . ,time) (z/read-org-title+mtime full))) (push (list rel title time) items))))) ;; Sort newest first by TIME (setq items (sort items (lambda (a b) (time-less-p (nth 2 b) (nth 2 a))))) ;; Trim (setq items (cl-subseq items 0 (min count (length items)))) ;; Write Org file (with-temp-file (site-path "home/recently-updated.org") (insert "#+TITLE: Recently Updated\n" "#+OPTIONS: toc:nil num:nil\n\n" "* Recently Updated (top 26 files - per lima's request)\n") (dolist (it items) (let* ((rel (nth 0 it)) (title (nth 1 it)) (time (nth 2 it)) (datestr (format-time-string "%Y-%m-%d %H:%M" time))) (insert (format "- [[file:%s][%s]] @@html:%s@@\n" rel title datestr)))))) (message "Wrote recently-updated.org")) (provide 'recently-updated)