From c44a52a7905b605a6537e3ff9bb4fe3afede0485 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 10 May 2026 14:34:52 -0500 Subject: refactor(cj-org-text): extract Org-safe text sanitizers from calendar-sync Phase 3 of utility-consolidation. Three sanitizers moved from calendar-sync.el into a new cj-org-text.el module so other consumers (web-clipper, AI conversation, mail-to-org capture) can compose Org content from external text without depending on calendar: - `calendar-sync--sanitize-org-body' -> `cj/org-sanitize-body-text' - `calendar-sync--sanitize-org-property-value' -> `cj/org-sanitize-property-value' - `calendar-sync--sanitize-org-heading' -> `cj/org-sanitize-heading' The helpers stay pure (string in, string out, nil-safe) and have no Org-mode dependency, so they work in batch and in tests without loading Org. Migrate calendar-sync.el to use the new public names: drop the three local defuns, add `(require \='cj-org-text)', update the six call sites in `calendar-sync--make-event-entry'. Move the existing 17-test file to `tests/test-cj-org-text-sanitize.el', rename test names to match the new helpers, add 1 nil-input test for `cj/org-sanitize-heading' that wasn't in the original file. Total: 18 Normal/Boundary tests across the three helpers. --- modules/calendar-sync.el | 38 +++++++------------------------------- 1 file changed, 7 insertions(+), 31 deletions(-) (limited to 'modules/calendar-sync.el') diff --git a/modules/calendar-sync.el b/modules/calendar-sync.el index f87d0192..890dd9df 100644 --- a/modules/calendar-sync.el +++ b/modules/calendar-sync.el @@ -71,6 +71,7 @@ (require 'cl-lib) (require 'subr-x) +(require 'cj-org-text) (defun calendar-sync--log-silently (format-string &rest args) "Log FORMAT-STRING with ARGS without requiring the full config." @@ -294,31 +295,6 @@ Returns nil for nil input. Returns empty string for whitespace-only input." (when text (string-trim (calendar-sync--strip-html (calendar-sync--unescape-ics-text text))))) -(defun calendar-sync--sanitize-org-body (text) - "Sanitize TEXT for safe inclusion as org body content. -Replaces leading asterisks with dashes to prevent lines from being -parsed as org headings. Handles multiple levels (e.g. ** becomes --)." - (when text - (replace-regexp-in-string - "^\\(\\*+\\) " - (lambda (match) - (concat (make-string (length (match-string 1 match)) ?-) " ")) - text))) - -(defun calendar-sync--sanitize-org-property-value (text) - "Sanitize TEXT for safe inclusion as a single Org property value." - (when text - (string-trim - (replace-regexp-in-string - "[[:space:]\n\r]+" - " " - text)))) - -(defun calendar-sync--sanitize-org-heading (text) - "Sanitize TEXT for safe inclusion as a single Org heading title." - (calendar-sync--sanitize-org-property-value - (calendar-sync--sanitize-org-body text))) - ;;; Date Utilities (defun calendar-sync--add-months (date months) @@ -1091,7 +1067,7 @@ Cleans text fields (description, location, summary) via `calendar-sync--clean-te "Convert parsed EVENT plist to org entry string. Produces property drawer with LOCATION, ORGANIZER, STATUS, URL when present. Description appears as body text after the drawer." - (let* ((summary (calendar-sync--sanitize-org-heading + (let* ((summary (cj/org-sanitize-heading (or (plist-get event :summary) "(No Title)"))) (description (plist-get event :description)) (location (plist-get event :location)) @@ -1106,22 +1082,22 @@ Description appears as body text after the drawer." ;; Collect non-nil properties (when (and location (not (string-empty-p location))) (push (format ":LOCATION: %s" - (calendar-sync--sanitize-org-property-value location)) + (cj/org-sanitize-property-value location)) props)) (when organizer (let ((org-name (or (plist-get organizer :cn) (plist-get organizer :email)))) (when org-name (push (format ":ORGANIZER: %s" - (calendar-sync--sanitize-org-property-value org-name)) + (cj/org-sanitize-property-value org-name)) props)))) (when (and status (not (string-empty-p status))) (push (format ":STATUS: %s" - (calendar-sync--sanitize-org-property-value status)) + (cj/org-sanitize-property-value status)) props)) (when (and url (not (string-empty-p url))) (push (format ":URL: %s" - (calendar-sync--sanitize-org-property-value url)) + (cj/org-sanitize-property-value url)) props)) (setq props (nreverse props)) ;; Build output @@ -1134,7 +1110,7 @@ Description appears as body text after the drawer." (push ":END:" parts)) ;; Add description as body text (sanitized to prevent org heading conflicts) (when (and description (not (string-empty-p description))) - (push (calendar-sync--sanitize-org-body description) parts)) + (push (cj/org-sanitize-body-text description) parts)) (string-join (nreverse parts) "\n")))) (defun calendar-sync--event-start-time (event) -- cgit v1.2.3