3 (defvar *info-tables
* (make-hash-table :test
'equal
))
5 ;; Gcl doesn't like equalp hashtables.
7 (make-hash-table :test
#'equal
)
8 "Hash table for looking up which html file contains the
9 documentation. The key is the topic we're looking for and the value
10 is a cons consisting of the html file and the id for the key.")
13 (defun print-prompt (prompt-count)
15 (maxima::format-prompt
17 (if (zerop prompt-count
)
18 (intl:gettext
"Enter space-separated numbers, `all' or `none': ")
19 (intl:gettext
"Still waiting: "))))
21 (defvar +select-by-keyword-alist
+
22 '((noop "") (all "a" "al" "all") (none "n" "no" "non" "none")))
24 (defun parse-user-choice (nitems)
26 with line
= (read-line #+(or sbcl cmu
) *standard-input
*) and nth and pos
= 0
27 while
(multiple-value-setq (nth pos
)
28 (parse-integer line
:start pos
:junk-allowed t
))
29 if
(or (minusp nth
) (>= nth nitems
))
30 do
(format *debug-io
* (intl:gettext
"~&Discarding invalid number ~d.") nth
)
31 else collect nth into list
36 '(#\space
#\tab
#\newline
#\
;) (subseq line pos))
37 +select-by-keyword-alist
+
38 :test
#'(lambda (item list
)
39 (member item list
:test
#'string-equal
))))))
42 (format *debug-io
* (intl:gettext
"~&Ignoring trailing garbage in input.")))
43 (return (cons keyword list
)))))
45 (defun select-info-items (selection items
)
49 collect
(nth i items
)))
53 ; ------------------------------------------------------------------
54 ; STUFF ABOVE SALVAGED FROM PREVIOUS INCARNATION OF SRC/CL-INFO.LISP
55 ; STUFF BELOW IS NEW, BASED ON LOOKUP TABLE BUILT AHEAD OF TIME
56 ; ------------------------------------------------------------------
58 ; ------------------ search help topics ------------------
60 (defun maxima::combine-path
(&rest list
)
61 "splice a '/' between the path components given as arguments"
62 (format nil
"~{~A~^/~}" list
))
64 (defun load-primary-index ()
65 ;; Is with-standard-io-syntax too much for what we want?
67 ((subdir-bit (or maxima
::*maxima-lang-subdir
* "."))
68 (path-to-index (maxima::combine-path maxima
::*maxima-infodir
* subdir-bit
"maxima-index.lisp"))
70 (maxima::combine-path maxima
::*maxima-infodir
* subdir-bit
"maxima-index-html.lisp")))
71 ;; Set the default of the html URL base to be a file URL pointing
73 (setf maxima
::$url_base
(concatenate 'string
75 (if maxima
::*maxima-lang-subdir
*
76 (maxima::combine-path maxima
::*maxima-htmldir
*
77 maxima
::*maxima-lang-subdir
*)
78 maxima
::*maxima-htmldir
*)))
81 (with-standard-io-syntax (load path-to-index
))
82 (error (condition) (warn (intl:gettext
(format nil
"~&Maxima is unable to set up the help system.~&(Details: CL-INFO::LOAD-PRIMARY-INDEX: ~a)~&" condition
)))))
84 (with-standard-io-syntax (load path-to-html-index
))
85 (error (condition) (warn (intl:gettext
(format nil
"~&Maxima is unable to set up the help system.~&(Details: CL-INFO::LOAD-PRIMARY-INDEX: ~a)~&" condition
)))))))
89 (setq x
(strip-quotes x
))
90 (let ((exact-matches (exact-topic-match x
)))
91 (if (not (some-exact x exact-matches
))
93 (format t
(intl:gettext
" No exact match found for topic `~a'.~% Try `?? ~a' (inexact match) instead.~%~%") x x
)
96 (display-items exact-matches
)
97 (if (some-inexact x
(inexact-topic-match x
))
98 (format t
(intl:gettext
" There are also some inexact matches for `~a'.~% Try `?? ~a' to see them.~%~%") x x
))
101 (defun some-exact (x matches
)
102 (some #'identity
(flatten-matches x matches
)))
104 (defun some-inexact (x matches
)
105 (some #'null
(flatten-matches x matches
)))
107 (defun flatten-matches (x matches
)
108 ;; OH GODS, SPARE YOUR SERVANT FROM YOUR FIERY WRATH ...
109 (mapcar #'(lambda (y) (equal y x
)) (mapcar #'first
(apply #'append
(mapcar #'second matches
)))))
111 (defun exact-topic-match (topic)
112 (setq topic
(regex-sanitize topic
))
113 (loop for dir-name being the hash-keys of
*info-tables
*
114 collect
(list dir-name
(exact-topic-match-1 topic dir-name
))))
116 (defun exact-topic-match-1 (topic d
)
118 ((section-table (first (gethash d
*info-tables
*)))
119 (defn-table (second (gethash d
*info-tables
*)))
120 (regex1 (concatenate 'string
"^" topic
"$"))
121 (regex2 (concatenate 'string
"^" topic
" *<[0-9]+>$")))
123 (find-regex-matches regex1 section-table
)
124 (find-regex-matches regex1 defn-table
)
125 (find-regex-matches regex2 section-table
)
126 (find-regex-matches regex2 defn-table
))))
128 (defun info-inexact (x)
129 (setq x
(strip-quotes x
))
130 (let ((inexact-matches (inexact-topic-match x
)))
131 (when inexact-matches
132 (display-items inexact-matches
))
133 (not (null inexact-matches
))))
135 ;; MATCHES looks like ((D1 (I11 I12 I12 ...)) (D2 (I21 I22 I23 ...)))
136 ;; Rearrange it to ((D1 I11) (D1 I12) (D1 I13) ... (D2 I21) (D2 I22) (D2 I23) ...)
137 (defun rearrange-matches (matches)
138 (apply #'append
(mapcar #'(lambda (di) (let ((d (first di
)) (i (second di
))) (mapcar #'(lambda (i1) (list d i1
)) i
))) matches
)))
140 (defun display-items (items)
142 ((items-list (rearrange-matches items
))
143 (nitems (length items-list
))
146 (loop for i from
0 for item in items-list do
149 ((heading-title (nth 4 (second item
)))
150 (item-name (first (second item
))))
151 (format t
"~% ~d: ~a~@[ (~a)~]" i item-name heading-title
))))
157 for prompt-count from
0
159 (finish-output *debug-io
*)
160 (print-prompt prompt-count
)
162 #-
(or sbcl cmu
) (clear-input)
164 (parse-user-choice nitems
) items-list
)))
165 #-
(or sbcl cmu
) (clear-input))
167 (finish-output *debug-io
*)
170 (funcall maxima
::*help-display-function
* wanted
)
173 (maxima::$describe_uses_html
174 (when maxima
::*debug-display-html-help
*
175 (format *debug-io
* "wanted = ~A~%" wanted
))
176 (loop for
(dir entry
) in wanted
177 do
(maxima::display-html-help
(car entry
))))
179 (loop for item in wanted
180 do
(let ((doc (read-info-text (first item
) (second item
))))
182 (format t
"~A~%~%" doc
)
183 (format t
"Unable to find documentation for `~A'.~%~
184 Possible bug maxima-index.lisp or build_index.pl?~%"
185 (first (second item
)))))))))))
187 (defun inexact-topic-match (topic)
188 (setq topic
(regex-sanitize topic
))
189 (let ((foo (loop for dir-name being the hash-keys of
*info-tables
*
190 collect
(list dir-name
(inexact-topic-match-1 topic dir-name
)))))
191 (remove-if #'(lambda (x) (null (second x
))) foo
)))
193 (defun inexact-topic-match-1 (topic d
)
195 ((section-table (first (gethash d
*info-tables
*)))
196 (defn-table (second (gethash d
*info-tables
*))))
198 (find-regex-matches topic section-table
)
199 (find-regex-matches topic defn-table
))))
201 ;; If S is enclosed in single quotes or double quotes,
202 ;; return the quoted string.
203 (defun strip-quotes (s)
204 (let ((n (length s
)))
205 (if (<= n
2) s
;; incidentally return "" or '' verbatim
206 (let ((first-char (aref s
0)) (last-char (aref s
(1- n
))))
207 (if (or (and (eql first-char
#\') (eql last-char
#\'))
208 (and (eql first-char
#\") (eql last-char
#\")))
212 (defun regex-sanitize (s)
213 "Precede any regex special characters with a backslash."
214 (pregexp:pregexp-quote s
))
216 (defun find-regex-matches (regex-string hashtable
)
218 ;; Do the search ignoring case by wrapping the regex-string in
220 ((regex (concatenate 'string
"(?i:" regex-string
")"))
223 #'(lambda (key value
)
224 (when (pregexp:pregexp-match-positions regex key
)
226 (format t
"key value: ~S ~S: match ~A~%"
227 key value
(pregexp:pregexp-match-positiions regex key
))
228 (setq regex-matches
(cons `(,key .
,value
) regex-matches
))))
230 (stable-sort regex-matches
#'string-lessp
:key
#'car
)))
232 (defun read-info-text (dir-name parameters
)
234 ((value (cdr parameters
))
235 (filename (car value
))
236 (byte-offset (cadr value
))
237 (char-count (caddr value
))
238 (text (make-string char-count
))
239 (path+filename
(merge-pathnames (make-pathname :name filename
) dir-name
)))
241 (with-open-file (in path
+filename
:direction
:input
)
242 (unless (plusp byte-offset
)
243 ;; If byte-offset isn't positive there must be some error in
244 ;; the index. Return nil and let the caller deal with it.
245 (return-from read-info-text nil
))
246 (file-position in byte-offset
)
247 (read-sequence text in
:start
0 :end char-count
)
249 (error () (maxima::merror
"Cannot find documentation for `~M': missing info file ~M~%"
250 (car parameters
) (namestring path
+filename
))))))
252 ; --------------- build help topic indices ---------------
254 (defun load-info-hashtables (dir-name deffn-defvr-pairs section-pairs
)
255 (if (and (zerop (length section-pairs
))
256 (zerop (length deffn-defvr-pairs
)))
257 (format t
(intl:gettext
"warning: ignoring an empty documentation index in ~a~%") dir-name
)
259 (section-hashtable deffn-defvr-hashtable
)
260 (ensure-info-tables dir-name
)
261 (mapc #'(lambda (x) (setf (gethash (car x
) section-hashtable
) (cdr x
))) section-pairs
)
262 (mapc #'(lambda (x) (setf (gethash (car x
) deffn-defvr-hashtable
) (cdr x
))) deffn-defvr-pairs
))))
264 (defun ensure-info-tables (dir-name)
265 (or (gethash dir-name
*info-tables
*)
267 ((t1 (make-hash-table :test
'equal
))
268 (t2 (make-hash-table :test
'equal
)))
269 (setf (gethash dir-name
*info-tables
*) (list t1 t2
)))))
271 (defun load-html-index (entries)
272 (clrhash *html-index
*)
273 ;; Go through the entries and add it to the html index hashtable.
274 ;; Each entry is a list of 3 items: the item key for the hash table,
275 ;; the path to the html file containing the item we're interested in
276 ;; and the html id for the item.
277 (dolist (entry entries
)
278 (destructuring-bind (item path id
)
280 (setf (gethash item
*html-index
*) (cons path id
)))))