3 (defvar *info-tables
* (make-hash-table :test
'equal
))
5 (defun print-prompt (prompt-count)
9 (if (zerop prompt-count
)
10 (intl:gettext
"Enter space-separated numbers, `all' or `none': ")
11 (intl:gettext
"Still waiting: "))))
13 (defvar +select-by-keyword-alist
+
14 '((noop "") (all "a" "al" "all") (none "n" "no" "non" "none")))
16 (defun parse-user-choice (nitems)
18 with line
= (read-line #+(or sbcl cmu
) *standard-input
*) and nth and pos
= 0
19 while
(multiple-value-setq (nth pos
)
20 (parse-integer line
:start pos
:junk-allowed t
))
21 if
(or (minusp nth
) (>= nth nitems
))
22 do
(format *debug-io
* (intl:gettext
"~&Discarding invalid number ~d.") nth
)
23 else collect nth into list
28 '(#\space
#\tab
#\newline
#\
;) (subseq line pos))
29 +select-by-keyword-alist
+
30 :test
#'(lambda (item list
)
31 (member item list
:test
#'string-equal
))))))
34 (format *debug-io
* (intl:gettext
"~&Ignoring trailing garbage in input.")))
35 (return (cons keyword list
)))))
37 (defun select-info-items (selection items
)
41 collect
(nth i items
)))
45 ; ------------------------------------------------------------------
46 ; STUFF ABOVE SALVAGED FROM PREVIOUS INCARNATION OF SRC/CL-INFO.LISP
47 ; STUFF BELOW IS NEW, BASED ON LOOKUP TABLE BUILT AHEAD OF TIME
48 ; ------------------------------------------------------------------
50 ; ------------------ search help topics ------------------
52 (defun maxima::combine-path
(&rest list
)
53 "splice a '/' between the path components given as arguments"
54 (format nil
"~{~A~^/~}" list
))
56 (defun load-primary-index ()
57 (declare (special maxima
::*maxima-lang-subdir
* maxima
::*maxima-infodir
*))
58 ;; Load the index, but make sure we use a sensible *read-base*.
59 ;; See bug 1951964. GCL doesn't seem to have
60 ;; with-standard-io-syntax. Is just binding *read-base* enough? Is
61 ;; with-standard-io-syntax too much for what we want?
63 ((subdir-bit (or maxima
::*maxima-lang-subdir
* "."))
64 (path-to-index (maxima::combine-path maxima
::*maxima-infodir
* subdir-bit
"maxima-index.lisp")))
67 (with-standard-io-syntax (load path-to-index
))
69 (let ((*read-base
* 10.
)) (load path-to-index
))
70 (error (condition) (warn (intl:gettext
(format nil
"~&Maxima is unable to set up the help system.~&(Details: CL-INFO::LOAD-PRIMARY-INDEX: ~a)~&" condition
)))))))
73 (let ((exact-matches (exact-topic-match x
)))
74 (if (not (some-exact x exact-matches
))
76 (format t
(intl:gettext
" No exact match found for topic `~a'.~% Try `?? ~a' (inexact match) instead.~%~%") x x
)
79 (display-items exact-matches
)
80 (if (some-inexact x
(inexact-topic-match x
))
81 (format t
(intl:gettext
" There are also some inexact matches for `~a'.~% Try `?? ~a' to see them.~%~%") x x
))
84 (defun some-exact (x matches
)
85 (some #'identity
(flatten-matches x matches
)))
87 (defun some-inexact (x matches
)
88 (some #'null
(flatten-matches x matches
)))
90 (defun flatten-matches (x matches
)
91 ;; OH GODS, SPARE YOUR SERVANT FROM YOUR FIERY WRATH ...
92 (mapcar #'(lambda (y) (equal y x
)) (mapcar #'first
(apply #'append
(mapcar #'second matches
)))))
94 (defun exact-topic-match (topic)
95 (setq topic
(regex-sanitize topic
))
96 (loop for dir-name being the hash-keys of
*info-tables
*
97 collect
(list dir-name
(exact-topic-match-1 topic dir-name
))))
99 (defun exact-topic-match-1 (topic d
)
101 ((section-table (first (gethash d
*info-tables
*)))
102 (defn-table (second (gethash d
*info-tables
*)))
103 (regex1 (concatenate 'string
"^" topic
"$"))
104 (regex2 (concatenate 'string
"^" topic
" *<[0-9]+>$")))
106 (find-regex-matches regex1 section-table
)
107 (find-regex-matches regex1 defn-table
)
108 (find-regex-matches regex2 section-table
)
109 (find-regex-matches regex2 defn-table
))))
111 (defun info-inexact (x)
112 (let ((inexact-matches (inexact-topic-match x
)))
113 (when inexact-matches
114 (display-items inexact-matches
))
115 (not (null inexact-matches
))))
117 ;; MATCHES looks like ((D1 (I11 I12 I12 ...)) (D2 (I21 I22 I23 ...)))
118 ;; Rearrange it to ((D1 I11) (D1 I12) (D1 I13) ... (D2 I21) (D2 I22) (D2 I23) ...)
119 (defun rearrange-matches (matches)
120 (apply #'append
(mapcar #'(lambda (di) (let ((d (first di
)) (i (second di
))) (mapcar #'(lambda (i1) (list d i1
)) i
))) matches
)))
122 (defun display-items (items)
124 ((items-list (rearrange-matches items
))
125 (nitems (length items-list
))
128 (loop for i from
0 for item in items-list do
131 ((heading-title (nth 4 (second item
)))
132 (item-name (first (second item
))))
133 (format t
"~% ~d: ~a~@[ (~a)~]" i item-name heading-title
))))
139 for prompt-count from
0
141 (finish-output *debug-io
*)
142 (print-prompt prompt-count
)
144 #-
(or sbcl cmu
) (clear-input)
146 (parse-user-choice nitems
) items-list
)))
147 #-
(or sbcl cmu
) (clear-input))
149 (finish-output *debug-io
*)
152 (loop for item in wanted
153 do
(let ((doc (read-info-text (first item
) (second item
))))
155 (format t
"~A~%~%" doc
)
156 (format t
"Unable to find documentation for `~A'.~%~
157 Possible bug maxima-index.lisp or build_index.pl?~%"
158 (first (second item
)))))))))
160 (defun inexact-topic-match (topic)
161 (setq topic
(regex-sanitize topic
))
162 (let ((foo (loop for dir-name being the hash-keys of
*info-tables
*
163 collect
(list dir-name
(inexact-topic-match-1 topic dir-name
)))))
164 (remove-if #'(lambda (x) (null (second x
))) foo
)))
166 (defun inexact-topic-match-1 (topic d
)
168 ((section-table (first (gethash d
*info-tables
*)))
169 (defn-table (second (gethash d
*info-tables
*))))
171 (find-regex-matches topic section-table
)
172 (find-regex-matches topic defn-table
))))
174 (defun regex-sanitize (s)
175 "Precede any regex special characters with a backslash."
177 ((L (coerce maxima-nregex
::*regex-special-chars
* 'list
)))
179 ; WORK AROUND NREGEX STRANGENESS: CARET (^) IS NOT ON LIST *REGEX-SPECIAL-CHARS*
180 ; INSTEAD OF CHANGING NREGEX (WITH POTENTIAL FOR INTRODUCING SUBTLE BUGS)
181 ; JUST APPEND CARET TO LIST HERE
182 (setq L
(cons #\^ L
))
184 (coerce (apply #'append
185 (mapcar #'(lambda (c) (if (member c L
:test
#'eq
)
186 `(#\\ ,c
) `(,c
))) (coerce s
'list
)))
189 (defun find-regex-matches (regex-string hashtable
)
191 ((regex (maxima-nregex::regex-compile regex-string
:case-sensitive nil
))
192 (regex-fcn (coerce regex
'function
))
195 #'(lambda (key value
)
196 (if (funcall regex-fcn key
)
197 (setq regex-matches
(cons `(,key .
,value
) regex-matches
))
200 (stable-sort regex-matches
#'string-lessp
:key
#'car
)))
202 (defun read-info-text (dir-name parameters
)
204 ((value (cdr parameters
))
205 (filename (car value
))
206 (byte-offset (cadr value
))
207 (char-count (caddr value
))
208 (text (make-string char-count
))
209 (path+filename
(merge-pathnames (make-pathname :name filename
) dir-name
)))
210 (with-open-file (in path
+filename
:direction
:input
)
211 (unless (plusp byte-offset
)
212 ;; If byte-offset isn't positive there must be some error in
213 ;; the index. Return nil and let the caller deal with it.
214 (return-from read-info-text nil
))
215 (file-position in byte-offset
)
217 #+gcl gcl-read-sequence
218 text in
:start
0 :end char-count
))
222 (defun gcl-read-sequence (s in
&key
(start 0) (end nil
))
223 (dotimes (i (- end start
))
224 (setf (aref s i
) (read-char in
))))
226 ; --------------- build help topic indices ---------------
228 (defun load-info-hashtables (dir-name deffn-defvr-pairs section-pairs
)
229 (if (and (zerop (length section-pairs
))
230 (zerop (length deffn-defvr-pairs
)))
231 (format t
(intl:gettext
"warning: ignoring an empty documentation index in ~a~%") dir-name
)
233 (section-hashtable deffn-defvr-hashtable
)
234 (ensure-info-tables dir-name
)
235 (mapc #'(lambda (x) (setf (gethash (car x
) section-hashtable
) (cdr x
))) section-pairs
)
236 (mapc #'(lambda (x) (setf (gethash (car x
) deffn-defvr-hashtable
) (cdr x
))) deffn-defvr-pairs
))))
238 (defun ensure-info-tables (dir-name)
239 (or (gethash dir-name
*info-tables
*)
241 ((t1 (make-hash-table :test
'equal
))
242 (t2 (make-hash-table :test
'equal
)))
243 (setf (gethash dir-name
*info-tables
*) (list t1 t2
)))))