Add org-agenda-api container builds and fly.io deployment
Consolidates container builds from colonelpanic-org-agenda-api repo: - Add org-agenda-api input to nixos flake - Add container-colonelpanic and container-kat package outputs - Add org-agenda-api cachix as substituter - Add org-agenda-api devShell for deployment work New org-agenda-api directory contains: - container.nix: Container build logic using mkContainer - configs/: Instance configs (custom-config.el, fly.toml, secrets) - deploy.sh: Fly.io deployment script - secrets.nix: agenix secret declarations Build with: nix build .#container-colonelpanic Deploy with: cd org-agenda-api && ./deploy.sh colonelpanic Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
171
org-agenda-api/configs/kat/custom-config.el
Normal file
171
org-agenda-api/configs/kat/custom-config.el
Normal file
@@ -0,0 +1,171 @@
|
||||
;;; custom-config.el --- Container config loader -*- lexical-binding: t; -*-
|
||||
|
||||
;; Helper function used by org-config (must be defined before loading preface)
|
||||
(defun imalison:join-paths (&rest paths)
|
||||
"Join PATHS together into a single path."
|
||||
(let ((result (car paths)))
|
||||
(dolist (p (cdr paths))
|
||||
(setq result (expand-file-name p result)))
|
||||
result))
|
||||
|
||||
;; Load tangled config files in order
|
||||
(let ((config-dir (file-name-directory load-file-name)))
|
||||
;; Load preface first (defines variables with default values)
|
||||
(when (file-exists-p (expand-file-name "org-config-preface.el" config-dir))
|
||||
(load (expand-file-name "org-config-preface.el" config-dir)))
|
||||
|
||||
;; Override paths for container environment AFTER loading preface
|
||||
;; Use setq to ensure we override the defvar values from preface
|
||||
(setq imalison:org-dir "/data/org")
|
||||
(setq imalison:shared-org-dir "/data/shared")
|
||||
|
||||
;; Re-derive all path variables using the container org-dir
|
||||
(setq imalison:org-gtd-file (imalison:join-paths imalison:org-dir "gtd.org"))
|
||||
(setq imalison:org-habits-file (imalison:join-paths imalison:org-dir "habits.org"))
|
||||
(setq imalison:org-calendar-file (imalison:join-paths imalison:org-dir "calendar.org"))
|
||||
(setq imalison:org-inbox-file (imalison:join-paths imalison:org-dir "inbox.org"))
|
||||
|
||||
;; Shared paths derived from shared-org-dir
|
||||
(setq imalison:shared-org-gtd-file (imalison:join-paths imalison:shared-org-dir "gtd.org"))
|
||||
(setq imalison:shared-habits-file (imalison:join-paths imalison:shared-org-dir "habits.org"))
|
||||
(setq imalison:shared-calendar-file (imalison:join-paths imalison:shared-org-dir "calendar.org"))
|
||||
(setq imalison:shared-shopping-file (imalison:join-paths imalison:shared-org-dir "shopping.org"))
|
||||
(setq imalison:shared-repeating-file (imalison:join-paths imalison:shared-org-dir "repeating.org"))
|
||||
(setq imalison:orgzly-files (list (imalison:join-paths imalison:org-dir "orgzly.org")))
|
||||
(setq imalison:repeating-org-files (list imalison:org-habits-file
|
||||
imalison:shared-habits-file
|
||||
imalison:shared-repeating-file))
|
||||
|
||||
;; org-config-custom.el uses customize format (var value), convert to setq
|
||||
(when (file-exists-p (expand-file-name "org-config-custom.el" config-dir))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents (expand-file-name "org-config-custom.el" config-dir))
|
||||
(goto-char (point-min))
|
||||
(condition-case nil
|
||||
(while t
|
||||
(let ((form (read (current-buffer))))
|
||||
(when (and (listp form) (symbolp (car form)))
|
||||
(set (car form) (eval (cadr form))))))
|
||||
(end-of-file nil))))
|
||||
|
||||
;; Load main config (sets up org-agenda-files using the variables we just set)
|
||||
(when (file-exists-p (expand-file-name "org-config-config.el" config-dir))
|
||||
(load (expand-file-name "org-config-config.el" config-dir)))
|
||||
|
||||
;; Load optional overrides (instance-specific customizations)
|
||||
(when (file-exists-p (expand-file-name "overrides.el" config-dir))
|
||||
(load (expand-file-name "overrides.el" config-dir))))
|
||||
|
||||
;; Define no-op stubs for unavailable packages (overwrite autoloads)
|
||||
(defun org-bullets-mode (&optional _arg)
|
||||
"No-op stub for org-bullets-mode (package not available in container)."
|
||||
nil)
|
||||
|
||||
;; Override shared-org-file-p to handle nil imalison:shared-org-dir
|
||||
;; The original calls (file-truename imalison:shared-org-dir) which errors when nil
|
||||
(defun imalison:shared-org-file-p ()
|
||||
"Check if current file is in the shared org directory.
|
||||
Returns nil if imalison:shared-org-dir is not set."
|
||||
(and imalison:shared-org-dir
|
||||
(string-prefix-p (file-truename imalison:shared-org-dir)
|
||||
(file-truename default-directory))))
|
||||
|
||||
;; Helper functions used by org-agenda-custom-commands
|
||||
;; These are defined in README.org but needed for custom views
|
||||
(defun imalison:compare-int-list (a b)
|
||||
"Compare two lists of integers lexicographically."
|
||||
(when (and a b)
|
||||
(cond ((> (car a) (car b)) 1)
|
||||
((< (car a) (car b)) -1)
|
||||
(t (imalison:compare-int-list (cdr a) (cdr b))))))
|
||||
|
||||
(defun get-date-created-from-agenda-entry (agenda-entry)
|
||||
"Get the CREATED property timestamp from an agenda entry."
|
||||
(org-time-string-to-time
|
||||
(org-entry-get (get-text-property 1 'org-marker agenda-entry) "CREATED")))
|
||||
|
||||
;; Auto-convert org-capture-templates to org-agenda-api-capture-templates
|
||||
(defun imalison:extract-template-string (template-spec)
|
||||
"Extract the template string from TEMPLATE-SPEC.
|
||||
Handles string templates, function templates, and file templates."
|
||||
(let ((template-part (nth 4 template-spec)))
|
||||
(cond
|
||||
((stringp template-part) template-part)
|
||||
((and (listp template-part)
|
||||
(eq (car template-part) 'function))
|
||||
;; Try to evaluate the function to get template string
|
||||
(condition-case nil
|
||||
(let ((result (funcall (eval (cadr template-part)))))
|
||||
(if (stringp result) result ""))
|
||||
(error "")))
|
||||
((and (listp template-part)
|
||||
(eq (car template-part) 'file))
|
||||
;; File template - read file contents
|
||||
(condition-case nil
|
||||
(with-temp-buffer
|
||||
(insert-file-contents (eval (cadr template-part)))
|
||||
(buffer-string))
|
||||
(error "")))
|
||||
(t ""))))
|
||||
|
||||
(defun imalison:infer-prompt-type (prompt-match)
|
||||
"Infer the prompt type from PROMPT-MATCH.
|
||||
PROMPT-MATCH is the full match string like \"%^{Name}t\" or \"%^{Title}\"."
|
||||
(let ((suffix (substring prompt-match (1- (length prompt-match)))))
|
||||
(cond
|
||||
((string-match-p "[tTuU]$" prompt-match) 'date)
|
||||
((string= "g" suffix) 'tag)
|
||||
((string= "G" suffix) 'tag)
|
||||
((string= "C" suffix) 'string) ; completion
|
||||
(t 'string))))
|
||||
|
||||
(defun imalison:extract-prompts-from-template (template-string)
|
||||
"Extract prompt definitions from TEMPLATE-STRING.
|
||||
Returns a list of (NAME :type TYPE :required t) for each %^{...} found."
|
||||
(let ((prompts '())
|
||||
(seen-names '())
|
||||
(pos 0))
|
||||
(while (string-match "%\\^{\\([^}|]+\\)\\(?:|[^}]*\\)?}\\([tTuUgGC]\\)?" template-string pos)
|
||||
(let* ((name (match-string 1 template-string))
|
||||
(full-match (match-string 0 template-string))
|
||||
(type (imalison:infer-prompt-type full-match)))
|
||||
(unless (member name seen-names)
|
||||
(push name seen-names)
|
||||
(push (list name :type type :required t) prompts))
|
||||
(setq pos (match-end 0))))
|
||||
(nreverse prompts)))
|
||||
|
||||
(defun imalison:convert-capture-template (template-spec)
|
||||
"Convert a single org-capture TEMPLATE-SPEC to API format.
|
||||
Returns nil for non-entry templates or templates that can't be converted."
|
||||
(when (and (listp template-spec)
|
||||
(>= (length template-spec) 4))
|
||||
(let* ((key (nth 0 template-spec))
|
||||
(description (nth 1 template-spec))
|
||||
(type (nth 2 template-spec)))
|
||||
;; Only convert entry-type templates, skip menu items (no type)
|
||||
(when (and (stringp key)
|
||||
(stringp description)
|
||||
(eq type 'entry))
|
||||
(let* ((template-string (imalison:extract-template-string template-spec))
|
||||
(prompts (imalison:extract-prompts-from-template template-string))
|
||||
;; Create a unique API key from the hotkey
|
||||
(api-key (concat "capture-" key)))
|
||||
(list api-key
|
||||
:name description
|
||||
:template template-spec
|
||||
:prompts prompts))))))
|
||||
|
||||
(defun imalison:convert-all-capture-templates ()
|
||||
"Convert all org-capture-templates to org-agenda-api-capture-templates format."
|
||||
(let ((converted '()))
|
||||
(dolist (template org-capture-templates)
|
||||
(let ((api-template (imalison:convert-capture-template template)))
|
||||
(when api-template
|
||||
(push api-template converted))))
|
||||
(nreverse converted)))
|
||||
|
||||
;; Auto-generate API capture templates from org-capture-templates
|
||||
(setq org-agenda-api-capture-templates (imalison:convert-all-capture-templates))
|
||||
|
||||
;;; custom-config.el ends here
|
||||
Reference in New Issue
Block a user