Mercurial > core / lisp/lib/doc/file.lisp
changeset 431: |
c40d2a41d7ce |
parent: |
d876b572b5b9
|
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Sun, 09 Jun 2024 02:04:18 -0400 |
permissions: |
-rw-r--r-- |
description: |
source concatenating std.lisp, more systems, got zstd simple working, IO work, added dat/tar |
1 ;;; lib/doc/file.lisp --- File Documentation 3 ;; Support for inline documentation and comments in source-code files. 7 ;; As of [2024-04-04] we aren't considering any other files besides 8 ;; Lisp source-code. We'll eventually open the door to other langs via 9 ;; SYN, and finally integrate with other types of 10 ;; documentation-specific files like READMEs. 14 ;; We determine the flavor of Lisp used in a source file by the file 15 ;; extension. In reality we're only dealing with two flavors: Common 16 ;; Lisp and Emacs Lisp. 18 ;; The way we treat them at the read stage is almost identical. The 19 ;; only difference being that Emacs Lisp does not support the inline 20 ;; comment syntax '#| some comment |#'. 22 ;; In any case the idea is to do 'something' with comments instead of 23 ;; getting rid of them at read-time. 27 ;; Special consideration is given to source-code 'header' blocks. In 28 ;; our own code, we use them as much as possible but haven't been 29 ;; using them to their full potential just yet. 31 ;; You will find most code, including this file begins with a block of 32 ;; the following form: 35 ;;; PATH --- SHORT-DESCRIPTION 47 ;; Note the difference in comment characters used between the 48 ;; lines. Headings start with 3, and the contents of those Headings 49 ;; start with 2. The first heading/section is an 'anonymous' or 'meta' 50 ;; section that should be considered required. All headings beneath it 51 ;; are 'named' sections. 'Code:' is the only required named section, 52 ;; so in the example above, we may exclude the 'Commentary:' section. 56 ;; We define headings according to the Emacs notion of the term, as 57 ;; used in outline-mode and org-mode. As mentioned, headings in source 58 ;; files begin with a minimum of 3 comment characters. For each 59 ;; additional comment character, the nested 'level' of the heading is 60 ;; increased and any non-header elements or header elements with a 61 ;; level greater than the top-level are nested inside that heading. 63 ;; 3 comment headings represent a level of 0. Any heading with a level 64 ;; > 0 is a Subheading. For example, we are in a subheading named 65 ;; 'Headings' of level 2, inside a subheading of level 1, inside a 66 ;; heading named 'Commentary'. 70 ;; NOTE 2024-04-05: sb-impl::read-comment takes an 'ignore' second 71 ;; arg, but the return value is always ignored anyways? 76 ;; asdf:source-file-type asdf:source-file-explicit-type 77 (defvar *source-file-types* nil) 79 (defmacro define-source-file* (type ext &optional opts shebangp &body body) 81 (let ((rname (symbolicate "READ-" type "-SOURCE-FILE")) 82 (wname (symbolicate "WRITE-" type "-SOURCE-FILE")) 83 #+nil (kw (sb-int:keywordicate type))) 85 (pushnew ',type *source-file-types*) 87 (with-open-file (,f path) 89 (defun ,wname (source path) 90 (with-open-file (,f path) 91 (write source :stream ,f))))))) 93 (define-source-file* rust "rs") 94 (define-source-file* shell "sh") 95 (define-source-file* makefile "mk") 96 (define-source-file* nushell "nu") 97 (define-source-file* common-lisp "lisp") 98 (define-source-file* emacs-lisp "el") 99 (define-source-file* scheme "scm") 100 (define-source-file* skel "sk") 101 (define-source-file* sxp "sxp") 103 (defconstant +max-file-heading-level+ 8) 104 (defconstant +min-file-heading-level+ 3) 106 (defclass file-heading () 107 ((name :initarg :name :type string) 108 (level :initform 0 :initarg :level :type (integer 0 #.+max-file-heading-level+)) 109 (contents :initarg :contents :type string))) 111 (defun heading-line-p (string) 112 (uiop:string-prefix-p ";;;" string)) 114 (defun read-comment-line (stream) 115 "Read a comment line from STREAM. Returns two values: the uncommented 116 string and a 'level' indicating how many comment characters were 117 stripped. Note that this level is NOT the same as the heading level." 118 (let ((level 0) (contents (organ::read-line stream))) 119 (loop for c = (char contents level) 120 until (not (char= c #\;)) 123 (if (zerop level) contents (subseq contents (1+ level))) 126 (defun read-file-heading (stream) 127 (destructuring-bind (name level) (read-comment-line stream) 128 (make-instance 'file-heading :name name :level level :contents ""))) 130 (defclass file-headline (file-heading) 131 ((summary :initarg :summary :type string) 132 (opts :initform nil :initarg :opts :type list))) 134 (defun read-file-headline-description (stream) 135 "Read a headline description returning a string. Second value is the 136 next heading line found or nil if EOF." 137 (apply #'concatenate 'string 138 (loop for l = (read-line stream) 139 until (heading-line-p l) 142 (defun headline-values-p (string) 143 (let ((found (search " --- " string))) 144 (values (subseq string 0 found) (when found (subseq string (+ found 5)))))) 146 (defun split-headline-values (string) 147 "Split the headline in STRING into individual values." 148 (multiple-value-bind (name rest) (headline-values-p string) 150 (multiple-value-bind (summary opts) (headline-values-p rest) 151 (values name summary opts)) 152 (values name nil nil)))) 154 (defun read-file-headline (stream) 155 (let ((line (read-comment-line stream))) ;; throw out second value 156 (multiple-value-bind (name summary opts) (split-headline-values line) 157 (make-instance 'file-headline 162 :contents (read-file-headline-description stream))))) 164 (defclass file-header () 165 ((headline :initarg :headline :type file-headline) 166 (headings :initarg :headings :type (array file-heading))) 167 (:documentation "A source-file header object containing a FILE-HEADLINE and array of 168 optional top-level FILE-HEADINGs.")) 170 (defun read-file-header (path) 171 "Read a FILE-HEADER from PATH which should be an INPUT-FILE-STREAM. 173 File headers always appear at the very start of a file so the stream 174 position is always assumed to be 0." 175 (with-open-file (f path :if-does-not-exist :error) 176 (let ((hl (read-file-headline f))) 177 (loop for l = (read-line f nil) 179 until (uiop:string-prefix-p ";;; Code:" l) 180 collect l into contents) 181 (make-instance 'file-header 186 ;; (defmacro define-file-heading (type slots)) 188 (defclass file-documentation () 189 ((path :initarg :path :type pathname :accessor doc-path) 190 (header :initarg :header :type file-header) 191 (contents :initarg :contents :type sequence) 192 (locations :initarg :locations :type sequence)) 193 (:documentation "An object containing the header, contents, and relevant 194 locations of a source file. This object should be the result of a 195 function like COMPILE-FILE-DOCUMENTATION. Note that this object only 196 contains inline comments. Symbol documentation such as this one will 197 not be captured in instances of this object.")) 199 (defmethod print-object ((self file-documentation) stream) 200 (print-unreadable-object (self stream :type t) 201 (format stream "~A" (doc-path self)))) 203 (defun file-documentation (path) 204 "Return the FILE-DOCUMENTATION for PATH." 205 (make-instance 'file-documentation 207 :header (read-file-header path)))