forked from edicl/hunchentoot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
make-docstrings.lisp
228 lines (196 loc) · 8.49 KB
/
make-docstrings.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
;; -*- Lisp -*-
(defpackage :make-docstrings
(:use :cl)
(:export #:parse-doc))
(in-package :make-docstrings)
(defclass formatting-stream (trivial-gray-streams:fundamental-character-input-stream)
((understream :initarg :understream
:reader understream)
(width :initarg :width
:initform (error "missing :width argument to formatting-stream creation")
:reader width)
(column :initform 0
:accessor column)
(word-wrap-p :initform t
:accessor word-wrap-p)
(word-buffer :initform (make-array 1000
:element-type 'character
:adjustable t
:fill-pointer 0)
:reader word-buffer)))
(defun write-char% (char stream)
(incf (column stream))
(write-char char (understream stream)))
(defun print-newline (stream)
(write-char #\Newline (understream stream))
(setf (column stream) 0))
(defun buffer-not-empty-p (stream)
(plusp (length (word-buffer stream))))
(defun maybe-flush-word (stream)
(when (buffer-not-empty-p stream)
(cond
((< (width stream) (+ (column stream) (length (word-buffer stream))))
(print-newline stream))
((plusp (column stream))
(write-char% #\Space stream)))
(loop for char across (word-buffer stream)
do (write-char% char stream))
(setf (fill-pointer (word-buffer stream)) 0)))
(defmethod trivial-gray-streams:stream-write-char ((stream formatting-stream) char)
(if (word-wrap-p stream)
(cond
((eql #\Space char)
(maybe-flush-word stream))
((eql #\Newline char)
(maybe-flush-word stream)
(print-newline stream))
(t
(vector-push-extend char (word-buffer stream))))
(write-char char (understream stream))))
(defmethod trivial-gray-streams:stream-line-column (stream)
(+ (column stream) (length (word-buffer stream))))
(defmethod trivial-gray-streams:stream-write-string ((stream formatting-stream) string &optional start end)
(loop for i from (or start 0) below (or end (length string))
do (write-char (char string i) stream)))
(defmethod trivial-gray-streams:stream-terpri ((stream formatting-stream))
(write-char #\Newline stream))
(defmethod close ((stream formatting-stream) &key abort)
(unless abort
(maybe-flush-word stream)))
(defmethod (setf word-wrap-p) :before (new-value (stream formatting-stream))
(maybe-flush-word stream)
(when (buffer-not-empty-p stream)
(print-newline stream)))
(defun test-wrap-stream (text)
(with-output-to-string (s)
(with-open-stream (s (make-instance 'formatting-stream :understream s :width 20))
(write-string text s)
(setf (word-wrap-p s) nil)
(format s "~&OFF~%")
(write-string text s)
(format s "~&ON~%")
(setf (word-wrap-p s) t)
(write-string text s))))
(defmacro replace-regexp (place regex replacement)
`(setf ,place (cl-ppcre:regex-replace-all ,regex ,place ,replacement)))
(defun collapse-whitespace (string)
(replace-regexp string "[ \\t]*\\n[ \\t]*" #.(make-string 1 :initial-element #\Newline))
(replace-regexp string "(?<!\\n)\\n" " ")
(remove #\Newline string))
(defvar *output*)
(defun xml-to-docstring% (node transform)
(stp:do-children (child node)
(typecase child
(stp:text
(write-string (funcall transform (stp:data child)) *output*))
(stp:element
(ecase (intern (string-upcase (stp:local-name child)) :keyword)
(:p
(terpri *output*)
(terpri *output*)
(xml-to-docstring% child transform))
((:a :code :tt :blockquote :span :ul)
(xml-to-docstring% child transform))
((:li)
(xml-to-docstring% child transform)
(terpri *output*))
((:ref :arg :em :i)
(xml-to-docstring% child (alexandria:compose #'string-upcase transform)))
((:sup)
;; skip
)
(:pre
(terpri *output*)
(terpri *output*)
(setf (word-wrap-p *output*) nil)
(xml-to-docstring% child #'identity)
(setf (word-wrap-p *output*) t)
(terpri *output*)))))))
(defun xml-to-docstring (description-node)
(with-output-to-string (s)
(with-open-stream (*output* (make-instance 'formatting-stream :understream s :width 75))
(xml-to-docstring% description-node #'collapse-whitespace))))
(defun maybe-qualify-name (name package-name)
(if (find #\: name)
name
(format nil "~A:~A" package-name name)))
(defun get-doc-entry-type (node)
(let ((basic-type (intern (string-upcase (stp:local-name node)) :keyword)))
(if (eq basic-type :function)
(if (stp:attribute-value node "generic") ; FIXME: "no" not recognized
:generic-function
:function)
basic-type)))
(defun skip-to (stream char)
(loop until (eql char (peek-char nil stream))
do (read-char stream)))
(defun get-simple-def-docstring (source-string position)
(with-input-from-string (s source-string :start (1+ position))
(read s) ; DEFUN/DEFVAR/DEFPARAMETER
(read s) ; name
(read s) ; argument list/initial value
(skip-to s #\")
(list :start (file-position s)
:text (read s)
:end (file-position s))))
(defun get-complex-def-docstring (source-string position)
(with-input-from-string (s source-string :start (1+ position))
(read s) ; DEFCLASS/DEFINE-CONDITION/DEFGENERIC
(read s) ; name
(read s) ; arguments/supers
(loop
(let* ((start-of-clause (file-position s))
(clause (read s)))
(when (eql (first clause) :documentation)
(file-position s start-of-clause)
(skip-to s #\()
(read-char s)
(read s) ; :DOCUMENTATION
(skip-to s #\")
(return (list :start (file-position s)
:text (read s)
:end (file-position s))))))))
(defun get-doc-function (type)
(case type
((:function :special-variable) 'get-simple-def-docstring)
((:generic-function :class) 'get-complex-def-docstring)))
(defun source-location-flatten (location-info)
(apply #'append (rest (find :location (rest location-info) :key #'first))))
(defvar *files*)
(defclass file ()
((file-pathname :initarg :file-pathname
:reader file-pathname)
(docstrings :initform nil
:accessor docstrings)
(contents :accessor contents)))
(defmethod initialize-instance :after ((file file) &key file-pathname)
(setf (slot-value file 'contents) (alexandria:read-file-into-string file-pathname)))
(defun get-file (pathname)
(or (gethash pathname *files*)
(setf (gethash pathname *files*)
(make-instance 'file
:file-pathname pathname))))
(defun record-docstring (doc-docstring get-doc-function symbol-name)
(let ((definitions (remove-if (lambda (definition)
(or (cl-ppcre:scan "(?i)^\\s*\\(defmethod\\s" (first definition))
(eql (first (second definition)) :error)))
(swank:find-definitions-for-emacs symbol-name))))
(case (length definitions)
(0 (warn "no source location for ~A" symbol-name))
(1 (let* ((source-location (source-location-flatten (first definitions)))
(file (get-file (getf source-location :file))))
(push (list* :doc-docstring doc-docstring
(funcall get-doc-function (contents file) (getf source-location :position)))
(docstrings file))))
(2 (warn "multiple source locations for ~A" symbol-name)))))
(defun parse-doc (pathname default-package-name)
(let ((*files* (make-hash-table :test #'equal)))
(xpath:with-namespaces (("clix" "http://bknr.net/clixdoc"))
(xpath:do-node-set (node (xpath:evaluate "//*[clix:description!='']" (cxml:parse pathname (stp:make-builder))))
(let ((type (get-doc-entry-type node))
(symbol-name (maybe-qualify-name (stp:attribute-value node "name") default-package-name)))
(xpath:do-node-set (description (xpath:evaluate "clix:description" node))
(alexandria:when-let (get-doc-function (get-doc-function type))
(record-docstring (xml-to-docstring description)
get-doc-function symbol-name))))))
*files*))