changelog shortlog graph tags branches changeset files revisions annotate raw help

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
2 (pkg:defpkg :organ
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)
6  (:export
7  ;; params
8  :*org-todo-keyword-types*
9  :*org-todo-keywords*
10  ;; vars
11  :org-todo-keyword-map
12  :org-heading-regexp
13  :org-file-property-regexp
14  :org-todo-keyword-regexp
15  :org-property-regexp
16  :org-tag-regexp
17  :org-element-types
18  :org-element-objects
19  ;; proto
20  :org-parse
21  :org-parse-lines
22  ;; classes
23  :org-element
24  :text
25  :org-kind
26  :org-file
27  :org-lines
28  :o-lines
29  :org-stream
30  :org-headline
31  :level
32  :props
33  :tags
34  :title
35  :state
36  :org-todo-keyword
37  :todo-type
38  :org-list
39  :org-tag
40  ;; fns
41  :org-todo-keyword-p
42  :org-tag-split
43  :read-org-file
44  :read-org-lines
45  :read-org-lines-from-string
46  :make-org-headline
47  :make-org-todo-keyword
48  :make-org-tag
49  ;; macs
50  :org-init))
51 
52 (in-package :organ)
53 
54 (defparameter *org-todo-keyword-types*
55  '(todo wip done))
56 
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'. ")
63 
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)))
68  (v (cadr kw)))
69  (assert (member v *org-todo-keyword-types*))
70  (setf (gethash k kws) v)))
71  kws))
72 
73 (defvar org-todo-keyword-map (org-todo-keyword-map))
74 
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))
79 
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_@#%:]+:)$"))
90 
91 (defun org-tag-split (tags)
92  (remove-if (lambda (s) (typep s '(string 0))) (cl-ppcre:split ":" tags)))
93 
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'")
101 
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'")
108 
109 (defgeneric org-parse (self)
110  (:documentation "Parse the text slot from ORG-ELEMENT."))
111 
112 (defgeneric org-parse-lines (self)
113  (:documentation "Parse the text slot from ORG-ELEMENT as a vector of lines."))
114 
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 "")))
118 
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)))
124 
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
128  collect
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)
132  (list i nil x)))))))
133 
134 (defclass org-stream (fundamental-stream)
135  ((stream :initarg :stream :reader stream-of)))
136 
137 (defclass org-file (org-element org-stream)
138  ((path :initarg :path :accessor path)
139  (kind :allocation :class :initform :file)))
140 
141 (defun read-org-file (path)
142  (make-instance 'org-file :path path :text (read-file-string path)))
143 
144 ;; (slot-value (read-org-file "~/org/notes.org") 'text)
145 
146 (defclass org-lines (org-element)
147  ((lines :initarg :lines :type vector :accessor o-lines)
148  (kind :allocation :class :initform :org-lines)))
149 
150 (defun read-org-lines (&optional stream)
151  (let ((slice (make-instance 'org-lines)))
152  (setf (o-lines slice)
153  (apply #'vector
154  (loop for l = (read-line stream nil :eof)
155  until (eq l :eof)
156  collect l)))
157  slice))
158 
159 (defun read-org-lines-from-string (str)
160  (with-input-from-string (s str) (read-org-lines s)))
161 
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 "")))
171 
172 (defun make-org-headline (text)
173  (org-init org-headline text))
174 
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)
180  ;; scan for headline
181  (cl-ppcre:scan org-headline-regexp line)
182  (declare (ignore _))
183  (when start
184  (loop for rs across reg-start
185  for re across reg-end
186  for i from 0
187  do
188  (if (= i 0)
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)
194  (if match
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))))
203  (when tag-str
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
207  self))
208 
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)))
212 
213 (defun make-org-todo-keyword (text &optional type)
214  (make-instance 'org-todo-keyword :text text :type type))
215 
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))
220  self))
221 
222 (defclass org-list (org-element)
223  ((kind :allocation :class :initform :org-list)))
224 
225 (defclass org-tag (org-element)
226  ((kind :allocation :class :initform :org-tag)))
227 
228 (defun make-org-tag (text) (org-init org-tag text))
229 
230 (defmethod org-parse ((self org-tag)) self) ;; nop
231 
232 (defclass org-block (org-element) ())
233 
234 (defclass org-paragraph (org-element) ())
235 
236 (provide :organ)