branch: externals/gnosis
commit 6a37459c03caf4d82b821c93f29acf40f5cc4195
Author: Thanos Apollo <[email protected]>
Commit: Thanos Apollo <[email protected]>

    [New Module] gnosis-org: Org parsing for gnosis nodes.
    
    * Replaces org-gnosis org parsing.
---
 gnosis-org.el | 190 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 190 insertions(+)

diff --git a/gnosis-org.el b/gnosis-org.el
new file mode 100644
index 0000000000..fc036cbf49
--- /dev/null
+++ b/gnosis-org.el
@@ -0,0 +1,190 @@
+;;; gnosis-org.el --- Org-mode parsing for gnosis nodes  -*- lexical-binding: 
t; -*-
+
+;; Copyright (C) 2024-2026  Free Software Foundation, Inc.
+
+;; Author: Thanos Apollo <[email protected]>
+;; Keywords: extensions
+
+;;; Commentary:
+
+;; Pure org-file parsing functions for gnosis.  No DB dependency.
+;; These functions parse org buffers to extract node data, links, and tags.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'org-element)
+
+(defun gnosis-org-adjust-title (input)
+  "Strip org link markup from INPUT, keeping only link descriptions.
+Converts [[id:xxx][Description]] to Description."
+  (replace-regexp-in-string
+   "\\[\\[id:[^]]+\\]\\[\\(.*?\\)\\]\\]"
+   "\\1"
+   input))
+
+(defun gnosis-org-get-id ()
+  "Return id for heading at point."
+  (save-excursion
+    (let ((heading-level (org-current-level))
+         (id (org-id-get)))
+      (cond (id id)
+           ((and (null id) (= heading-level 1))
+            (goto-char (point-min))
+            (org-id-get))
+           (t
+            (outline-up-heading 1 t)
+            (gnosis-org-get-id))))))
+
+(defun gnosis-org-collect-id-links ()
+  "Collect ID links and current headline ID as (link-id . headline-id) pairs."
+  (let ((links nil)
+        (begin (point-min))
+        (end (point-max)))
+    (save-excursion
+      (goto-char begin)
+      (while (re-search-forward org-link-any-re end t)
+        (let ((link (match-string-no-properties 0)))
+          (when (string-match "id:\\([^]]+\\)" link)
+            (let ((target-id (match-string 1 link))
+                  (source-id (gnosis-org-get-id)))
+              (when (and target-id source-id)
+                (push (cons target-id source-id) links)))))))
+    (nreverse links)))
+
+(defun gnosis-org-get-filetags (&optional parsed-data)
+  "Return the filetags of the buffer's PARSED-DATA as a list of strings."
+  (let* ((parsed-data (or parsed-data (org-element-parse-buffer)))
+         (filetags (org-element-map parsed-data 'keyword
+                     (lambda (kw)
+                       (when (string-equal (org-element-property :key kw) 
"FILETAGS")
+                         (org-element-property :value kw)))
+                     nil t)))
+    (when (and filetags (not (string-empty-p (string-trim filetags))))
+      (remove "" (split-string filetags ":")))))
+
+(defun gnosis-org-get-data--topic (&optional parsed-data)
+  "Retrieve the title and ID from the current org buffer or given PARSED-DATA.
+Returns (title tags id).  ID will be nil if no file-level ID exists."
+  (unless parsed-data
+    (setq parsed-data (org-element-parse-buffer)))
+  (let* ((id (org-element-map parsed-data 'property-drawer
+               (lambda (drawer)
+                 (let ((parent (org-element-property :parent drawer)))
+                   (when (and parent
+                              (eq (org-element-type parent) 'section)
+                              (let ((section-parent (org-element-property 
:parent parent)))
+                                (eq (org-element-type section-parent) 
'org-data)))
+                     (org-element-map (org-element-contents drawer) 
'node-property
+                       (lambda (prop)
+                         (when (string= (org-element-property :key prop) "ID")
+                           (org-element-property :value prop)))
+                       nil t))))
+               nil t))
+        (title-raw (org-element-map parsed-data 'keyword
+                      (lambda (kw)
+                        (when (string= (org-element-property :key kw) "TITLE")
+                          (org-element-property :value kw)))
+                      nil t))
+        (title (when title-raw (gnosis-org-adjust-title title-raw)))
+        (tags (gnosis-org-get-filetags parsed-data)))
+    (unless (and title (not (string-empty-p title)))
+      (error "Org buffer must have a non-empty TITLE"))
+    (list title tags id)))
+
+(defun gnosis-org--combine-tags (inherited-tags headline-tags)
+  "Combine INHERITED-TAGS and HEADLINE-TAGS, removing duplicates."
+  (delete-dups (append (or inherited-tags '()) (or headline-tags '()))))
+
+(defun gnosis-org--parse-headlines-recursive (element parent-id parent-title 
parent-tags)
+  "Recursively parse headlines from ELEMENT.
+ELEMENT can be the parsed-data (org-data) or a headline element.
+PARENT-ID is the ID of nearest ancestor with ID (or 0).
+PARENT-TITLE is the hierarchical title path (only from ancestors with IDs).
+PARENT-TAGS are the inherited tags from ancestors."
+  (let (results)
+    (org-element-map (org-element-contents element) 'headline
+      (lambda (headline)
+        (let* ((current-id (org-element-property :ID headline))
+               (title (org-element-property :raw-value headline))
+               (level (org-element-property :level headline))
+               (headline-tags (org-element-property :tags headline))
+               (combined-tags (gnosis-org--combine-tags parent-tags 
headline-tags)))
+          (if current-id
+              (let* ((clean-title (gnosis-org-adjust-title (string-trim 
title)))
+                     (full-title (if parent-title
+                                     (concat parent-title ":" clean-title)
+                                   clean-title))
+                     (entry (list :id current-id
+                                  :title full-title
+                                  :tags combined-tags
+                                  :master (or parent-id 0)
+                                  :level level))
+                     (children (gnosis-org--parse-headlines-recursive
+                               headline
+                               current-id
+                               full-title
+                               combined-tags)))
+                (setq results (append results (cons entry children))))
+            (let ((children (gnosis-org--parse-headlines-recursive
+                             headline
+                             parent-id
+                             parent-title
+                             combined-tags)))
+              (setq results (append results children))))))
+      nil nil 'headline)
+    results))
+
+(defun gnosis-org-buffer-data (&optional data)
+  "Parse DATA in current buffer for topics & headlines with their ID, tags, 
links."
+  (let* ((parsed-data (or data (org-element-parse-buffer)))
+         (topic-info (gnosis-org-get-data--topic parsed-data))
+         (topic-title (nth 0 topic-info))
+         (topic-tags (nth 1 topic-info))
+         (topic-id (nth 2 topic-info))
+         (headlines (gnosis-org--parse-headlines-recursive
+                     parsed-data
+                     topic-id
+                     (when topic-id topic-title)
+                     topic-tags)))
+    (if topic-id
+        (cons (list :title topic-title
+                    :id topic-id
+                    :tags topic-tags
+                    :master 0
+                    :level 0)
+              headlines)
+      headlines)))
+
+(defun gnosis-org-get-file-info (filename)
+  "Get data for FILENAME.
+Returns file data with FILENAME."
+  (with-temp-buffer
+    (insert-file-contents filename)
+    (org-mode)
+    (org-set-regexps-and-options 'tags-only)
+    (let* ((data (gnosis-org-buffer-data))
+          (links (gnosis-org-collect-id-links)))
+      (append data (list links)))))
+
+(defun gnosis-org--file-hash (file)
+  "Compute SHA1 hash of FILE content."
+  (with-temp-buffer
+    (insert-file-contents file)
+    (secure-hash 'sha1 (current-buffer))))
+
+(defun gnosis-org--create-name (title &optional timestring gpg-p default-gpg 
default-timestring)
+  "Create filename for TITLE.
+TIMESTRING defaults to DEFAULT-TIMESTRING.
+GPG-P: when non-nil, add .gpg suffix (overrides DEFAULT-GPG)."
+  (let ((timestring (or timestring default-timestring "%Y%m%d%H%M%S"))
+       (filename (replace-regexp-in-string "#" ""
+                                           (replace-regexp-in-string " " "_" 
title)))
+       (use-gpg (if (eq gpg-p 'default)
+                    default-gpg
+                  (or gpg-p default-gpg))))
+    (format "%s--%s.org%s" (format-time-string timestring) filename
+           (if use-gpg ".gpg" ""))))
+
+(provide 'gnosis-org)
+;;; gnosis-org.el ends here

Reply via email to