changeset 678: |
2b7d5a8d63ac |
parent: |
bb8aa1eda12b
|
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Wed, 25 Sep 2024 21:39:39 -0400 |
permissions: |
-rw-r--r-- |
description: |
alien octets fix, workin with org-graph-db |
1 ;;; graph.el --- Graph-oriented Extensions -*- lexical-binding: t; -*- 3 ;; Copyright (C) 2024 The Compiler Company 5 ;; Author: Richard Westhaver <richard.westhaver@gmail.com> 6 ;; Keywords: docs, maint, outlines, extensions 8 ;; This program is free software; you can redistribute it and/or modify 9 ;; it under the terms of the GNU General Public License as published by 10 ;; the Free Software Foundation, either version 3 of the License, or 11 ;; (at your option) any later version. 13 ;; This program is distributed in the hope that it will be useful, 14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 ;; GNU General Public License for more details. 18 ;; You should have received a copy of the GNU General Public License 19 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 34 (defcustom org-graph-db-directory (join-paths user-org-stash-directory "graph") 35 "graph database storage directory." 39 (defcustom org-graph-locations (list (join-paths company-org-directory "notes/")) 40 "List of directories to check for nodes." 41 :type '(list directory) 44 (defcustom org-graph-include-agenda-files nil 45 "When non-nil, include `org-agenda-files' in the graph." 49 (defcustom org-graph-include-archive nil 50 "When non-nil, include `org-arhive-location' in the graph." 54 (defcustom org-graph-include-org-directory nil 55 "When non-nil, include `org-directory' files in the graph." 59 (defcustom org-graph-compaction-hook nil 60 "Hook run when a graph is compacted to `org-graph-db'." 64 (defcustom org-graph-capture-hook nil 65 "Hook run when a node is added to the graph." 69 (defcustom org-graph-db-init-script (join-paths company-source-directory "infra/scripts/org-db-init.lisp") 70 "Path to a lisp script responsible for initializing the `org-graph-db-directory'.") 72 (cl-defstruct org-graph-db-handle 83 (defcustom org-graph-db (make-org-graph-db-handle) 84 "A handle to the database backend which stores nodes and edges." 85 :type 'org-graph-db-handle 88 (defun org-graph-from-id-locations (&optional edges local) 89 "Populate the `org-graph' from `org-id-locations', filtering out any 90 entries not under a member of `org-graph-locations'. When EDGES is 91 non-nil visit each node and collect all edges found." 94 (let* ((node-ids (copy-hash-table (org-id-locations-load))) ;; don't overwrite `org-id-locations' 95 (graph (make-org-graph :nodes node-ids))) 98 (if-let ((ok (cl-loop for l in org-graph-locations 99 when (string-prefix-p l (file-truename v)) 101 (let ((pos (cdr (org-id-find-id-in-file k v)))) 102 (message "%s %s" k v) 103 (org-with-file-buffer v 105 (org-graph-node-at-point graph) 106 (when edges (org-graph-edges-at-point graph)))) 107 (remhash k (org-graph-nodes graph)))) 108 (org-graph-nodes graph)) 110 (setq-local org-graph graph) 111 (setq org-graph graph))))) 113 (defun org-graph-files () 114 (org-list-files org-graph-locations org-agenda-extensions)) 116 (cl-defstruct org-graph 117 ;; TODO 2024-09-17: use integers instead of string 118 (nodes (make-hash-table :test 'equal)) 119 (edges (make-hash-table :test 'equal))) 121 (defvar org-graph (make-org-graph) 122 "The Emacs-native org-graph. Should be assigned to an `org-graph' instance.") 124 (cl-defstruct org-graph-node id name file point) 125 (cl-defstruct org-graph-edge (type 'link) in properties timestamp point out) 127 (defun org-graph--file-hash (file) 128 "Compute the hash of FILE." 130 (set-buffer-multibyte nil) 131 (insert-file-contents-literally file) 132 (secure-hash 'md5 (current-buffer)))) 134 (defun org-graph-node-at-point (&optional update) 135 "Return the `org-graph-node' at point. When UPDATE is non-nil insert or 136 update the node into the org-graph object specified or when 't' use the 137 currently active org-graph." 138 (let* ((file (buffer-file-name)) 139 (node (make-org-graph-node :point (point) :file file))) 140 (if (derived-mode-p 'org-mode) 142 (if (org-before-first-heading-p) 143 (setf (org-graph-node-name node) (org-get-title) 144 ;; use the filename, create a hash as id 145 (org-graph-node-id node) (org-graph--file-hash file)) 146 (setf (org-graph-node-id node) (org-id-get) 147 (org-graph-node-name node) (elt (org-heading-components) 4)))) 148 (setf (org-graph-node-id node) (org-graph--file-hash file) 149 (org-graph-node-name node) (file-name-nondirectory file))) 151 (puthash (org-graph-node-id node) node (org-graph-nodes (if (eql t update) org-graph update)))) 154 ;; TODO 2024-09-22: properties 155 (defun org-graph-collect-edge () 156 "Collect the edge at point which should be a line created with `org-graph-edge--insert'." 157 (org-with-point-at (beginning-of-line) 158 (when (org-at-timestamp-p 'lax) 160 (ts (match-string-no-properties 0)) 163 ;; next 2 chars are the arrow 164 (let ((arrow (org-graph-edge-arrow* (buffer-substring-no-properties (point) (+ 2 (point)))))) 165 (goto-char (+ (point) 4)) 166 (make-org-graph-edge :in (org-id-get) 169 :timestamp (org-parse-time-string ts t) 170 :out (string-trim (org--link-at-point) "id:"))))))) 172 (defun org-graph-map-edges (function) 173 "Eval FUNCTION once for each edge in node at point with point at start of the edge." 174 (with-org-graph-edge-drawer (end) 175 (re-search-backward (rx bol ?: (literal (org-graph-edge-drawer)) ?: eol) nil t) 176 (goto-char (1+ (match-end 0))) 177 (cl-loop while (> (point-max) end (point)) 178 collect (funcall function) 182 (defun org-link-info (link) 183 (let ((path (org-element-property :path link)) 184 (type (org-element-property :type link)) 185 (desc (substring-no-properties (nth 2 link)))) 186 (list type path desc))) 189 (defun org-graph-infer-edges () 190 "Infer edges from the contents of the node at point. The result of this 191 function is a list of org-graph-edge objects." 193 (with-org-graph-edge-drawer (beg) 194 (org-element-map (org-element-parse-buffer) 'link 197 ;; (org-graph-edge-link-builder (funcall 'org-element-create link)) 200 (defun org-graph-reduce-edges (function) 201 "Same as `cl-reduce' where SEQ is the list of edges at point. FUNCTION 202 takes two `org-graph-edge' objects as input." 203 (let ((edges (org-graph-map-edges 'org-graph-collect-edge))) 204 (cl-reduce function edges))) 206 (defun org-graph-collect-edges-at-point (&optional update) 207 "Collect the contents of the EDGES drawer from node at point. When UPDATE 208 is non-nil insert or update the node into the org-graph object specified 209 or when 't' use the currently active org-graph." 210 (let ((edges (org-graph-map-edges 'org-graph-collect-edge))) 214 (org-graph-edge-in e) 216 (org-graph-edges (if (eql t update) org-graph update)))) 220 (defun org-graph-edge-equal (a b) 221 "Return non-nil if A and B are 'equal' org-graph-edge objects." 222 (equal (org-graph-edge-out a) (org-graph-edge-out b))) 224 (defun org-graph-edge-remove-duplicates () 225 "Remove duplicate edge entries from node at point." 226 (org-graph-reduce-edges 228 (when (org-graph-edge-equal a b) 229 (let ((tsa (org-graph-edge-timestamp a)) 230 (tsb (org-graph-edge-timestamp b))) 231 (goto-char (org-graph-edge-point (if (org-time> tsa tsb) b a))) 234 (defun org-graph-edges-at-point (&optional update) 235 "Return a list of `org-graph-edge' instances associated with the node at 236 point. When UPDATE is non-nil insert or update the edges into the 237 currently active org-graph." 239 (when (derived-mode-p 'org-mode) 240 (org-graph-collect-edges-at-point update))) 242 (defun org-graph-buffer-update (&optional buffer) 243 "Map over an org buffer adding all nodes to the active org-graph." 246 (with-current-buffer (or buffer (current-buffer)) 248 (goto-char (point-min)) 249 (org-graph-node-at-point t) 250 (when (derived-mode-p 'org-mode) 251 (org-map-entries (lambda () (org-graph-node-at-point t))))))) 254 ;; See https://github.com/toshism/org-super-links/blob/develop/org-super-links.el 255 (declare-function org-make-link-description-function "ext:org-mode") 257 (defvar org-graph-edge-drawer "EDGES" 258 "Controls how/where to insert edges. If nil edges will just be inserted 261 ;; TODO 2024-09-16: edge properties 262 (defvar org-graph-edge-prefix 'org-graph-edge-prefix-timestamp 263 "Prefix to insert before the edge. 264 This can be a string, nil, or a function that takes no arguments and 267 Default is the function `org-graph-edge-prefix-timestamp' 268 which returns an inactive timestamp formatted according to the variable 269 `org-time-stamp-formats'.") 271 ;; TODO 2024-09-16: do we need this? what sort of information for a 272 ;; given edge would go in the postfix? this may be better suited as a 273 ;; per-edge value rather than global - maybe use for comments. 274 (defvar org-graph-edge-postfix nil 275 "Postfix to insert after the edge. 276 This can be a string, nil, or a function that takes no arguments and 279 (defvar org-graph-edge-link-prefix nil 280 "Prefix to insert before the link. 281 This can be a string, nil, or a function that takes no arguments and 284 (defvar org-graph-edge-link-postfix nil 285 "Postfix to insert after the link. 286 This can be a string, nil, or a function that takes no arguments and 289 (defvar org-graph-edge-default-description-formatter org-make-link-description-function 290 "What to use if no description is provided. 291 This can be a string, nil or a function that accepts two arguments 292 LINK and DESC and returns a string. 294 nil will return the default desciption or the link. 295 string will be used only as a default fall back if set. 296 function will be called for every link. 298 Default is the variable `org-make-link-desciption-function'.") 300 (defvar org-graph-edge-search-function 'org-graph-edge-get-location 301 "The interface to use for finding target links. If you provide a custom 302 function it will be called with the `point` at the location the link 303 should be inserted. The only other requirement is that it should call 304 the function `org-graph-edge-insert-link-marker' with a marker to the target 305 link. AKA the place you want the edge. 307 `org-graph-edge-get-location' internally uses `org-refile-get-location'.") 309 (defvar org-graph-edge-pre-link-hook nil 310 "Hook called before storing the link on the link side. 311 This is called with point at the location where it was called.") 313 (defvar org-graph-edge-pre-backlink-hook nil 314 "Hook called before storing the link on the backlink side. 315 This is called with point in the heading of the backlink.") 317 (defvar org-graph-edge-indicator-alist 323 "An alist of (EDGE-TYPE . INDICATOR) pairs. Each INDICATOR is a string 324 which will be printed between the properties and backlink of the 325 associated EDGE-TYPE.") 327 (defun org-graph-edge-arrow (sym) 328 (cdr (assoc sym org-graph-edge-indicator-alist))) 330 (defun org-graph-edge-arrow* (str) 331 "Reverse lookup of edge arrow symbol." 332 (car (rassoc str org-graph-edge-indicator-alist))) 334 (defun org-graph-edge-get-location () 335 "Default for function `org-graph-edge-search-function' that reuses the `org-refile' machinery." 336 (org-refile-get-location "Node")) 338 (cl-defmacro with-org-graph-edge-drawer ((start &optional create) &rest body) 339 "START is a symbol which is bound to the start of the edge drawer." 342 (org-with-wide-buffer 343 (let ((org-log-into-drawer (org-graph-edge-drawer))) 344 (org-graph-narrow-to-node) 345 (let ((,start (org-log-beginning ,create))) 346 (when (or (re-search-forward (rx bol ?: "END" ?: eol) nil t) 347 (re-search-backward (rx bol ?: "END" ?: eol) nil t)) 351 (defun org-graph-edge-search-function () 352 "Call the search interface specified in variable `org-graph-edge-search-function'." 353 (funcall org-graph-edge-search-function)) 355 (defun org-graph-edge-prefix () 356 "Return an appropriate string based on variable `org-graph-edge-prefix'." 357 (cond ((equal org-graph-edge-prefix nil) "") 358 ((stringp org-graph-edge-prefix) org-graph-edge-prefix) 359 (t (funcall org-graph-edge-prefix)))) 361 (defun org-graph-edge-postfix () 362 "Return an appropriate string based on variable `org-graph-edge-postfix'." 363 (cond ((equal org-graph-edge-postfix nil) "\n") 364 ((stringp org-graph-edge-postfix) org-graph-edge-postfix) 365 (t (funcall org-graph-edge-postfix)))) 367 (defun org-graph-edge-link-prefix () 368 "Return an appropriate string based on variable `org-graph-edge-link-prefix'." 369 (cond ((equal org-graph-edge-link-prefix nil) "") 370 ((stringp org-graph-edge-link-prefix) org-graph-edge-link-prefix) 371 (t (funcall org-graph-edge-link-prefix)))) 373 (defun org-graph-edge-link-postfix () 374 "Return an appropriate string based on variable `org-graph-edge-link-postfix'." 375 (cond ((equal org-graph-edge-link-postfix nil) "") 376 ((stringp org-graph-edge-link-postfix) org-graph-edge-link-postfix) 377 (t (funcall org-graph-edge-link-postfix)))) 379 ;; TODO 2024-09-16: edge-properties 380 (defun org-graph-edge-prefix-timestamp () 381 "Return the default prefix string for an edge. 382 Inactive timestamp formatted according to `org-time-stamp-formats'." 383 (format-time-string (org-time-stamp-format t t) (current-time))) 385 (defun org-graph-edge-default-description-formatter (link desc) 386 "Return a string to use as the link desciption. 387 LINK is the link target. DESC is the provided desc." 388 (let ((p org-graph-edge-default-description-formatter)) 389 (cond ((equal p nil) (or desc link)) 390 ((stringp p) (or desc p)) 391 ((fboundp p) (funcall p link desc)) 394 (defun org-graph-edge-drawer () 395 "Name of the edge drawer, as a string, or nil. 396 This is the value of variable 397 `org-graph-edge-drawer'. However, if the current 398 entry has or inherits a EDGE_DRAWER property, it will be 399 used instead of the default value." 400 (let ((p (org-entry-get nil "EDGE_DRAWER" 'inherit t))) 401 (cond ((equal p "nil") nil) 403 (t org-graph-edge-drawer)))) 405 (defun org-graph-narrow-to-node () 406 "Narrow to current heading, excluding subheadings." 407 (org-narrow-to-subtree) 409 (org-next-visible-heading 1) 410 (narrow-to-region (point-min) (point)))) 412 ;; delete related functions 413 (defun org-graph-find-links (id) 414 "Return link elements for ID." 415 (org-graph-narrow-to-node) 417 (org-element-map (org-element-parse-buffer) 'link 419 (when (string= (org-element-property :path link) id) 424 (defun org-graph-edge--in-drawer-p () 425 "Return non-nil if point is in drawer. Value is element at point." 426 (let ((element (org-element-at-point))) 428 (not (memq (org-element-type element) '(drawer property-drawer)))) 429 (setq element (org-element-property :parent element))) 432 (defun org-graph-edge--delete-link (link) 433 "Delete the LINK. If point is in edges drawer, delete the entire line." 435 (goto-char (org-element-property :begin link)) 436 (if (org-graph-edge--in-drawer) 439 (org-remove-empty-drawer-at (point))) 440 (delete-region (org-element-property :begin link) (org-element-property :end link))))) 442 (defun org-graph-edge--insert (link desc arrow &rest props) 443 "Insert an edge at point. ARROW is a symbol representing the type of 444 arrow to insert. The rest of the arguments are parsed as :KEY VAL pairs 445 which are inserted with the edge." 446 (insert (format "%s %s " (org-graph-edge-prefix) 447 (org-graph-edge-arrow arrow))) 448 (org-insert-link nil link desc) 449 (insert (org-graph-edge-link-postfix)) 452 (defun org-graph-edge-insert-related (link desc) 453 "Insert a relation edge." 454 (with-org-graph-edge-drawer (beg t) 455 (org-graph-edge--insert link desc 'relation) 456 (org-indent-region beg (point)))) 458 (defun org-graph-edge-insert-backlink (link desc) 459 "Insert a backlink edge." 460 (with-org-graph-edge-drawer (beg t) 461 (let ((description (org-graph-edge-default-description-formatter link desc))) 462 (org-graph-edge--insert link description 'backlink) 463 (org-indent-region beg (point))))) 465 (defun org-graph-edge-insert-link (link desc) 466 "insert a forward link edge. When BACKLINK is non-nil also create a 467 backlink at the node specified in LINK." 469 (with-org-graph-edge-drawer (beg t) 470 (let ((description (org-graph-edge-default-description-formatter link desc))) 471 (org-graph-edge--insert link desc 'link) 472 (org-indent-region beg (point))))) 474 (defun org-graph-edge-links-action (marker hooks) 475 "Go to MARKER, run HOOKS and store a link." 476 (with-current-buffer (marker-buffer marker) 479 (widen) ;; buffer could be narrowed 480 (goto-char (marker-position marker)) 482 (call-interactively #'org-store-link) 483 (pop org-stored-links))))) 485 (defun org-graph-edge-link-builder (link) 486 "Format link description for LINK." 487 (let* ((link-ref (car link)) 488 (pre-desc (cadr link)) 489 (description (org-graph-edge-default-description-formatter link-ref pre-desc))) 490 (cons link-ref description))) 492 (defun org-graph-edge-insert-link-marker (target &optional no-forward no-backward) 493 "Insert link to marker TARGET and create an edge. 494 Only create edges in files in `org-mode' or a derived mode, otherwise just 495 act like a normal link. 497 If NO-FORWARD is non-nil skip creating the forward link. If NO-BACKWARD 498 is non-nil skip creating the backlink." 499 (let* ((source (point-marker)) 500 (source-link (org-graph-edge-links-action source 'org-graph-edge-pre-link-hook)) 501 (target-link (org-graph-edge-links-action target 'org-graph-edge-pre-backlink-hook)) 502 (source-formatted-link (org-graph-edge-link-builder source-link)) 503 (target-formatted-link (org-graph-edge-link-builder target-link))) 505 (with-current-buffer (marker-buffer target) 508 (widen) ;; buffer could be narrowed 509 (goto-char (marker-position target)) 510 (when (derived-mode-p 'org-mode) 511 (org-graph-edge-insert-backlink (car source-formatted-link) (cdr source-formatted-link))))))) 513 (with-current-buffer (marker-buffer source) 515 (goto-char (marker-position source)) 516 (print target-formatted-link) 517 (org-graph-edge-insert-link (car target-formatted-link) (cdr target-formatted-link))))))) 520 (defun org-graph-edge-convert-link (&optional arg) 521 "Convert a normal `org-mode' link at `point' to a graph link, ARG prefix. 522 When called interactively with a `C-u' prefix argument do not modify 525 (let ((from-m (point-marker)) 526 (target (save-window-excursion 527 (with-current-buffer (current-buffer) 531 (org-graph-edge-insert-link-marker target arg) 532 (goto-char (marker-position from-m))) 534 (let ((begin (org-element-property :begin (org-element-context))) 535 (end (org-element-property :end (org-element-context)))) 536 (delete-region begin end)))) 539 (defun org-graph-edge-delete () 540 "Delete the link at point, and the corresponding backlink. 541 If no backlink exists, just delete link at point. This works from 542 either side, and deletes both sides of a link." 544 (save-window-excursion 545 (with-current-buffer (current-buffer) 547 (let ((id (org-id-get (point)))) 549 (let ((link-elements (org-graph-find-edges id))) 551 (if (> (length link-elements) 1) 552 (error "Multiple links found.") 553 (org-graph-edge--delete-link (car link-elements))) 554 (message "No edge found. Deleting active only."))))))) 555 (org-graph-edge--delete-link (org-element-context))) 558 (defun org-graph-edge-insert () 559 "Insert an edge from `org-stored-links'." 563 (org-link-open (pop org-stored-links)) 564 (org-graph-edge-insert-link-marker (set-marker (make-marker) (point)))) 565 (org-graph-edge-link))) 568 (defun org-graph-edge-link (&optional no-backlink) 569 "Insert a link edge and add a backlink edge to the target heading. With 570 'C-u' don't create a backlink to the target." 572 (let ((target (org-graph-edge-search-function))) 573 (org-graph-edge-insert-link-marker (set-marker (make-marker) (car (cdddr target)) 574 (get-file-buffer (car (cdr target)))) 577 (defun org-graph-edge-backlink () 578 "Insert a backlink edge to the target heading from the current one." 580 (let ((target (org-graph-edge-search-function))) 581 (org-graph-edge-insert-link-marker (set-marker (make-marker) (car (cdddr target)) 582 (get-file-buffer (car (cdr target)))) 585 (defun org-dblock-write:links () 586 "Generate a 'links' block for the designated node.") 588 (defun org-dblock-write:graph () 589 "Generate a 'graph' block for the designated set of nodes.") 592 ;; graph.el ends here