Mercurial > core / lisp/lib/organ/organ.lisp
changeset 54: |
83f6c62bf2a8 |
parent: |
61482ce290f9
|
author: |
ellis <ellis@rwest.io> |
date: |
Fri, 24 Nov 2023 21:12:36 -0500 |
permissions: |
-rw-r--r-- |
description: |
cleanup |
1 ;;; organ.lisp --- Org parser 3 (:use :cl :cl-ppcre :std/sym :std/fu :organ/lexer) 4 (:shadowing-import-from :sb-gray :fundamental-stream) 5 (:import-from :uiop :read-file-string) 8 :*org-todo-keyword-types* 13 :org-file-property-regexp 14 :org-todo-keyword-regexp 45 :read-org-lines-from-string 47 :make-org-todo-keyword 54 (defparameter *org-todo-keyword-types* 57 (defparameter *org-todo-keywords* 58 '(("TODO" todo) ("DONE" done) ("FIND" todo) ("FOUND" done) 59 ("RESEARCH" todo) ("RECORD" todo) ("OUTLINE" todo) ("DRAFT" todo) 60 ("REVIEW" todo) ("FIX" todo) ("IMPL" todo) ("TEST" todo) ("FIXED" done) 61 ("GOTO" todo) ("HACK" todo) ("NOTE" todo) ("CODE" todo) ("LINK" todo)) 62 "List of keywords accepted by `organ'. ") 64 (defun org-todo-keyword-map () 65 (let ((kws (make-hash-table :size 20))) 66 (dolist (kw *org-todo-keywords*) 67 (let ((k (intern (car kw))) 69 (assert (member v *org-todo-keyword-types*)) 70 (setf (gethash k kws) v))) 73 (defvar org-todo-keyword-map (org-todo-keyword-map)) 75 (defmacro org-todo-keyword-p (kw) 76 "Search for symbol KW in `org-todo-keyword-map' returning the 77 associated value or nil if not found." 78 `(gethash (intern ,kw) org-todo-keyword-map)) 80 (defvar org-headline-regexp (cl-ppcre:parse-string "^([*]+)\\s+(.*)$")) 81 (defvar org-todo-keyword-regexp (cl-ppcre:parse-string "^(\\w+)\\s+(.*)$")) 82 (defvar org-file-property-regexp (cl-ppcre:parse-string "^[#+](.*)[:]\\s+(.*)$")) 83 (defvar org-property-regexp (cl-ppcre:parse-string "^[:](.*)[:]\\s+(.*)$")) 84 ;; this doesn't consume leading whitespace. It could be useful in the 85 ;; future to infer a value for org-tags-column but is contained in the 86 ;; title slot of `org-headline' for now. The result of this scan is a 87 ;; single string delimited by the ':' character. To get a list of tags 88 ;; as strings, use `org-tag-split'. 89 (defvar org-tag-regexp (cl-ppcre:parse-string "(:[\\w_@#%:]+:)$")) 91 (defun org-tag-split (tags) 92 (remove-if (lambda (s) (typep s '(string 0))) (cl-ppcre:split ":" tags))) 94 (defvar org-element-types 95 '(babel-call center-block clock comment comment-block diary-sexp drawer 96 dynamic-block example-block export-block fixed-width footnote-definition 97 headline horizontal-rule inlinetask item keyword latex-environment 98 node-property paragraph plain-list planning property-drawer quote-block 99 section special-block src-block table table-row verse-block) 100 "List of all org-element types provided by org-element.el in 'org-element-all-elements'") 102 (defvar org-element-objects 103 '(bold citation citation-reference code entity export-snippet 104 footnote-reference inline-babel-call inline-src-block italic 105 line-break latex-fragment link macro radio-target statistics-cookie 106 strike-through subscript superscript table-cell target timestamp underline verbatim) 107 "List of all org-element objects provided by org-element.el in 'org-element-all-objects'") 109 (defgeneric org-parse (self) 110 (:documentation "Parse the text slot from ORG-ELEMENT.")) 112 (defgeneric org-parse-lines (self) 113 (:documentation "Parse the text slot from ORG-ELEMENT as a vector of lines.")) 115 (defmacro org-init (class &optional text) 116 "Initialize a instance of `org-element' CLASS with optional TEXT." 117 `(make-instance ',class :text ,(or text ""))) 119 ;; parent and children are implicit. A single instance of 120 ;; `org-element' contains a complete org-mode AST. 121 (defclass org-element () 122 ((text :initarg :text :accessor text :type string) 123 (kind :initarg :kind :accessor org-kind :type keyword))) 125 (defmethod org-parse-lines ((self org-element)) 126 (let ((lines (o-lines (read-org-lines-from-string (slot-value self 'text))))) 127 (loop for i from 1 for x across lines 129 (if (cl-ppcre:scan org-headline-regexp x) (list i (symb 'headline) x) 130 (if (cl-ppcre:scan org-file-property-regexp x) (list i 'file-property x) 131 (if (cl-ppcre:scan org-property-regexp x) (list i 'node-property x) 134 (defclass org-stream (fundamental-stream) 135 ((stream :initarg :stream :reader stream-of))) 137 (defclass org-file (org-element org-stream) 138 ((path :initarg :path :accessor path) 139 (kind :allocation :class :initform :file))) 141 (defun read-org-file (path) 142 (make-instance 'org-file :path path :text (read-file-string path))) 144 ;; (slot-value (read-org-file "~/org/notes.org") 'text) 146 (defclass org-lines (org-element) 147 ((lines :initarg :lines :type vector :accessor o-lines) 148 (kind :allocation :class :initform :org-lines))) 150 (defun read-org-lines (&optional stream) 151 (let ((slice (make-instance 'org-lines))) 152 (setf (o-lines slice) 154 (loop for l = (read-line stream nil :eof) 159 (defun read-org-lines-from-string (str) 160 (with-input-from-string (s str) (read-org-lines s))) 162 ;; when level=0, headline is uninitialized 163 (defclass org-headline (org-element) 164 ((kind :allocation :class :initform :org-headline) 165 (state :accessor state :initform nil) 166 (level :accessor level :initform 0) 167 (props :accessor props :initform nil) 168 (priority :accessor priority :initform nil) 169 (tags :accessor tags :initform nil) 170 (title :accessor title :initform ""))) 172 (defun make-org-headline (text) 173 (org-init org-headline text)) 175 (defmethod org-parse ((self org-headline)) 176 (with-input-from-string (s (text self)) 177 (when (peek-char #\* s) ;; start headline 178 (let ((line (read-line s))) 179 (multiple-value-bind (start _ reg-start reg-end) 181 (cl-ppcre:scan org-headline-regexp line) 184 (loop for rs across reg-start 185 for re across reg-end 189 (setf (level self) (- re rs)) 190 (let ((sub (subseq line rs))) 191 (multiple-value-bind (match subs) 192 ;; scan for todo-keyword 193 (cl-ppcre:scan-to-strings org-todo-keyword-regexp sub) 195 (let ((k (svref subs 0))) 196 (if (org-todo-keyword-p k) 197 (setf (state self) (make-org-todo-keyword k) 198 (title self) (svref subs 1)) 199 (setf (title self) match))) 200 (setf (title self) sub)))))))) 201 ;; scan for tags, modifies title slot 202 (let ((tag-str (cl-ppcre:scan-to-strings org-tag-regexp (title self)))) 204 (setf (tags self) (apply #'vector (mapcar #'make-org-tag (org-tag-split tag-str))) 205 (title self) (subseq (title self) 0 (- (length (title self)) (length tag-str)))))))) 206 ;; TODO 2023-07-24: cookies,priority 209 (defclass org-todo-keyword (org-element) 210 ((kind :allocation :class :initform :org-todo-keyword) 211 (todo-type :accessor todo-type :initarg :type :initform nil :type symbol))) 213 (defun make-org-todo-keyword (text &optional type) 214 (make-instance 'org-todo-keyword :text text :type type)) 216 (defmethod org-parse ((self org-todo-keyword)) 217 (let* ((text (text self)) 218 (type (gethash (intern text) org-todo-keyword-map nil))) 219 (when type (setf (todo-type self) type)) 222 (defclass org-list (org-element) 223 ((kind :allocation :class :initform :org-list))) 225 (defclass org-tag (org-element) 226 ((kind :allocation :class :initform :org-tag))) 228 (defun make-org-tag (text) (org-init org-tag text)) 230 (defmethod org-parse ((self org-tag)) self) ;; nop 232 (defclass org-block (org-element) ()) 234 (defclass org-paragraph (org-element) ())