forked from ruricolist/serapeum
-
Notifications
You must be signed in to change notification settings - Fork 0
/
docs.lisp
executable file
·155 lines (140 loc) · 6.02 KB
/
docs.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
(in-package #:cl-user)
(eval-when (:compile-toplevel :load-toplevel :execute)
(ql:quickload '(:serapeum :cl-ppcre :swank) :silent t))
(defpackage #:serapeum.docs
(:use #:cl #:alexandria #:serapeum)
(:import-from #:swank/backend #:find-source-location)
(:export
#:render-function-reference-as-markdown
#:update-function-reference))
(in-package #:serapeum.docs)
(defun ungensym (s)
"If S is a gensym, chop the numbers off the end.
This saves needless updates to the documentation."
(if (symbolp s)
(let ((name (ppcre:regex-replace "\\d{2,}$" (string s) "")))
(if-let (p (symbol-package s))
(intern name p)
(make-symbol name)))
s))
(defun arglist (s)
(mapcar #'ungensym (swank-backend:arglist s)))
(defun asdf-components (comp)
(etypecase comp
(asdf:c-source-file nil)
(asdf:cl-source-file (list comp))
(asdf::static-file nil)
(asdf::component
(loop for c in (funcall
#+asdf3 #'asdf:component-children
#-asdf3 #'asdf:module-components
comp)
append (asdf-components c)))))
(defun function-reference-data (package-name system-name)
(let* ((package (find-package package-name))
(data (collect-reference-data package))
(system (asdf:find-system system-name))
(by-file (assort data :key (op (getf _ :file)) :test 'equal))
(components (asdf-components system))
(order (ordering (mapcar (op (namestring (slot-value _ 'asdf::absolute-pathname)))
components)
:test 'equal)))
(sort (loop for defs in by-file
for file = (getf (first defs) :file)
collect (cons file (sort defs
#'<
:key (op (getf _ :position)))))
order
:key #'car)))
(defun collect-reference-data (package)
(collecting
(do-external-symbols (s package)
(when (and (fboundp s)
(eql (symbol-package s) package))
(let* ((name s)
(type (symbol-function-type s))
(documentation (documentation s 'function))
(args (arglist s))
(location (cdr (swank-backend:find-source-location s)))
(file (assocadr :file location))
(position (assocadr :position location))
(line-number (1+ (count #\Newline
(read-file-into-string file)
:end position))))
(collect (list :name name
:type type
:documentation (or documentation
"NO DOCS!")
:args args
:file file
:position position
:line-number line-number)))))))
(defun symbol-function-type (s)
(cond ((macro-function s) :macro)
((typep (fdefinition s) 'generic-function) :generic-function)
((find-class s nil) :type)
(t :function)))
(defun render-function-reference-as-markdown (package-names system-name &key stream)
(labels ((render (stream)
(let ((data
(mappend (op (function-reference-data _ system-name))
(ensure-list package-names))))
(format stream "# Function Listing For ~a (~d files, ~d functions)~2%"
system-name
(length data)
(reduce #'+ data :key (op (length (cdr _)))))
;; Table of contents
(loop for (file . nil) in data do
(format stream "~&- [~a](#~a)~%"
(pathname-title file)
(pathname-name file))
finally (terpri stream))
;; Each file.
(loop for (file . defs) in data do
(let (*print-pretty*) ;Keep long arg lists from overflowing.
(format stream "~&## ~a~2%"
(pathname-title file)))
(let ((intro-file
(merge-pathnames
(make-pathname :type "md")
file)))
(when (uiop:file-exists-p intro-file)
(with-input-from-file (in intro-file :element-type 'character)
(copy-stream in stream))
(format stream "~2%")))
;; Each definition.
(dolist (def defs)
(let* ((docs
(ppcre:regex-replace-all "`([^ ]+?)'"
(getf def :documentation)
"`\\1`"))
(*print-case* :downcase)
(*package* (symbol-package (getf def :name))))
(format stream "~&### `~a`~2%~a~2%[View source](~a#L~a)~2%"
(cons (getf def :name) (getf def :args))
docs
(file-name-nonsystem (getf def :file) system-name)
(getf def :line-number))))))))
(etypecase stream
(stream
(render stream))
(null
(with-output-to-string (s)
(render s)))
((or string pathname)
(with-output-to-file (out stream
:if-exists :supersede)
(render out))))))
(defun file-name-nonsystem (fn system)
(enough-namestring fn (system-base system)))
(defun system-base (system)
(asdf:system-relative-pathname system ""))
(defun pathname-title (file)
(fmt "~{~:(~a~)~^ ~}" (split-sequence #\- (pathname-name file))))
(defun update-function-reference ()
(render-function-reference-as-markdown
'(:serapeum :serapeum.exporting :serapeum/contrib/hooks)
"serapeum"
:stream (asdf:system-relative-pathname :serapeum "REFERENCE.md")))
(update-function-reference)
(uiop:quit)