scribble: #:doc-id argument to load-xref and associated plumbing

Keep track of the target document name (i.e., the name of the
directory that will contain the target document) for a cross
reference, when known. This identification enables a simpler dynamic
resolution of a hyperlink in almost all cases, istead of requiring
a search for an arbitrary corss-reference key.

Indirect links still need a mapping of cross-reference keys
to document locations, but the mapping can be pruned to just
section and module-name keys.

This change cuts a generated "local-redirect.js" for the main
distribution to 1/8 of its size.
This commit is contained in:
Matthew Flatt 2014-03-15 15:19:21 -06:00
parent 6182541889
commit a956918adb
12 changed files with 210 additions and 71 deletions

View File

@ -6,6 +6,7 @@
setup/main-collects
setup/path-to-relative
setup/dirs
(only-in scribble/manual secref)
(only-in scribble/base-render render%)
(only-in scribble/html-render render-mixin)
(only-in scribble/xref load-xref make-data+root)))
@ -176,10 +177,15 @@ are not available, the query link is left unmodified to go through a
server.
The @filepath{local-redirect.js} and @filepath{local-user-redirect.js}
files embed a copy of the cross-reference database, where the copy is
specialized and compacted. These JavaScript files are generated as
part of the special @filepath{local-redirect} document that is
implemented by the @pkgname{racket-index} package.
files map documentation-directory names to specific paths. Most query
references contain a documentation-directory name and a relative path
within the directory, in which case the mapping from directory names
to paths is sufficient. Indirect links, such as those created by
@racket[(seclink #:indirect? #t ...)], embed a cross-reference key, and
so @filepath{local-redirect.js} and @filepath{local-user-redirect.js}
must also embed a part of the cross-reference database. The JavaScript
files are generated as part of the special @filepath{local-redirect}
document that is implemented by the @pkgname{racket-index} package.
The indirection through @filepath{local-redirect.js} and
@filepath{local-user-redirect.js} reduces the problem of relative
@ -187,7 +193,7 @@ links to the problem of referencing those two files. They are
referenced as absolute paths in a user-specific document build. To
create a @tech{built package} or @tech{binary package} that includes
documentation, each @filepath{.html} file must be modified to remove
the absolute path, and then each @filepath{.html} file must be
the absolute paths, and then each @filepath{.html} file must be
modified again on installation to put the target installation's paths
in path.

View File

@ -1,4 +1,4 @@
#lang scheme/base
#lang racket/base
(provide (all-defined-out))

View File

@ -3,9 +3,11 @@
racket/serialize
racket/class
racket/match
racket/set
setup/dirs
net/url
scribble/html-properties
setup/xref
"index-scope.rkt")
(provide make-local-redirect)
@ -13,17 +15,17 @@
(define (rewrite-code user?)
(define prefix (if user? "user_" ""))
@string-append|{
function |@|prefix|bsearch(str, start, end) {
function |@|prefix|bsearch(str, a, start, end) {
if (start >= end)
return false;
else {
var mid = Math.floor((start + end) / 2);
if (|@|prefix|link_targets[mid][0] == str)
if (a[mid][0] == str)
return mid;
else if (|@|prefix|link_targets[mid][0] < str)
return |@|prefix|bsearch(str, mid+1, end);
else if (a[mid][0] < str)
return |@|prefix|bsearch(str, a, mid+1, end);
else
return |@|prefix|bsearch(str, start, mid);
return |@|prefix|bsearch(str, a, start, mid);
}
}
@ -33,9 +35,26 @@
var elements = document.getElementsByClassName("Sq");
for (var i = 0; i < elements.length; i++) {
var elem = elements[i];
var n = elem.href.match(/tag=[^&]*/);
if (n) {
var pos = |@|prefix|bsearch(decodeURIComponent(n[0].substring(4)),
var doc = elem.href.match(/doc=[^&]*/);
var rel = elem.href.match(/rel=[^&]*/);
var tag = elem.href.match(/tag=[^&]*/);
if (doc && rel) {
var pos = |@|prefix|bsearch(decodeURIComponent(doc[0].substring(4)),
|@|prefix|link_dirs,
0,
|@|prefix|link_dirs.length);
if (pos) {
var p = |@|prefix|link_dirs[pos][1];
if (|@|prefix|link_target_prefix) {
p = |@|prefix|link_target_prefix + p;
}
elem.href = p + "/" + decodeURIComponent(rel[0].substring(4));
tag = false;
}
}
if (tag) {
var pos = |@|prefix|bsearch(decodeURIComponent(tag[0].substring(4)),
|@|prefix|link_targets,
0,
|@|prefix|link_targets.length);
if (pos) {
@ -43,7 +62,6 @@
if (|@|prefix|link_target_prefix) {
p = |@|prefix|link_target_prefix + p;
}
elem.href = p;
}
}
}
@ -82,11 +100,18 @@
#f
null
(lambda (renderer p ri)
(define doc-dirs (get-rendered-doc-directories (not user?) user?))
(define keys (if (and main-at-user? (not user?))
;; If there's no installation-scope "doc", then
;; the "main" redirection table is useless.
null
(resolve-get-keys #f ri (lambda (v) #t))))
(resolve-get-keys #f ri
(lambda (v)
;; Support key-based indirect only on sections
;; and module names:
(define t (car v))
(or (eq? t 'part)
(eq? t 'mod-path))))))
(define (target? v) (and (vector? v) (= 5 (vector-length v))))
(define dest-dir (send renderer get-dest-directory #t))
(define (make-dest user?)
@ -124,13 +149,26 @@
(path->directory-path
(build-path (find-doc-dir) "local-redirect")))))
(newline o))
(fprintf o "var ~alink_dirs = [" (if user? "user_" ""))
(define (extract-name e)
(let-values ([(base name dir?) (split-path e)])
(path->string name)))
(for ([e (in-list (sort doc-dirs string<? #:key extract-name))]
[i (in-naturals)])
(define name (extract-name e))
(fprintf o (if (zero? i) "\n" ",\n"))
(fprintf o " [~s, ~s]" name (if user?
(url->string (path->url e))
(format "../~a" name))))
(fprintf o "];\n\n")
(fprintf o "var ~alink_targets = [" (if user? "user_" ""))
(for ([e (in-list db)]
[i (in-naturals)])
(fprintf o (if (zero? i) "\n" ",\n"))
(fprintf o " [~s, ~s]" (car e) (cadr e)))
(fprintf o "];\n\n")
(fprintf o (rewrite-code user?))))
(fprintf o (rewrite-code user?))
(newline o)))
(unless (file-exists? alt-dest)
;; make empty alternate file; in `user?` mode, this
;; file will get used only when "racket-index" is not

View File

@ -10,11 +10,16 @@
setup/doc-db)
(provide load-collections-xref
make-collections-xref)
make-collections-xref
get-rendered-doc-directories)
(define cached-xref #f)
(define (get-dests tag no-user? no-main?)
(define (get-rendered-doc-directories no-user? no-main?)
(append (get-dests 'scribblings no-user? no-main? #f)
(get-dests 'rendered-scribblings no-user? no-main? #f)))
(define (get-dests tag no-user? no-main? sxrefs?)
(define main-dirs
(for/hash ([k (in-list (find-relevant-directories (list tag) 'no-user))])
(values k #t)))
@ -42,10 +47,12 @@
(if no-user? 'never 'false-if-missing)
#:main? (not no-main?))])
(if d
(for*/list ([i (in-range (add1 out-count))]
[p (in-value (build-path d (format "out~a.sxref" i)))]
#:when (file-exists? p))
p)
(if sxrefs?
(for*/list ([i (in-range (add1 out-count))]
[p (in-value (build-path d (format "out~a.sxref" i)))]
#:when (file-exists? p))
p)
(list d))
null))
null)))))
@ -62,11 +69,19 @@
(exn-message exn)
(format "~e" exn))))
#f)])
(make-data+root
(make-data+root+doc-id
;; data to deserialize:
(cadr (call-with-input-file* dest fasl->s-exp))
;; provide a root for deserialization:
(path-only dest))))))
(path-only dest)
;; Use the destination directory's name as an identifier,
;; which allows a faster and more compact indirection
;; for installation-scaoped documentation:
(let-values ([(base name dir?) (split-path dest)])
(and (path? base)
(let-values ([(base name dir?) (split-path base)])
(and (path? name)
(path->string name))))))))))
(define (make-key->source db-path no-user? no-main? quiet-fail? register-shutdown!)
(define main-db (and (not no-main?)
@ -132,8 +147,8 @@
(define (get-reader-thunks no-user? no-main? quiet-fail? done-ht)
(map (dest->source done-ht quiet-fail?)
(filter values (append (get-dests 'scribblings no-user? no-main?)
(get-dests 'rendered-scribblings no-user? no-main?)))))
(filter values (append (get-dests 'scribblings no-user? no-main? #t)
(get-dests 'rendered-scribblings no-user? no-main? #t)))))
(define (load-collections-xref [report-loading void])
(or cached-xref

View File

@ -1354,6 +1354,16 @@ whether the resulting information originated from an external source
(i.e., a different document).}
@defproc[(resolve-get/ext-id [p (or/c part? #f)] [ri resolve-info?] [key info-key?])
(values any/c (or/c boolean? string?))]{
Like @racket[render-get/ext?], but the second result can be a string
to indicate the source document's identification as established via
@racket[load-xref] and a @racket[#:doc-id] argument.
@history[#:added "1.1"]}
@defproc[(resolve-search [dep-key any/c] [p (or/c part? #f)] [ri resolve-info?] [key info-key?])
void?]{

View File

@ -320,10 +320,16 @@ Specializes a @racket[render<%>] class for generating HTML output.
@defmethod[(set-external-tag-path [url string?]) void?]{
Configures the renderer to redirect links to external via
@racket[url], adding a @racket[tag] query element to the end of the
Configures the renderer to redirect links to external documents via
@racket[url], adding a @tt{tag} query element to the end of the
URL that contains the Base64-encoded, @racket[print]ed, serialized
original tag (in the sense of @racket[link-element]) for the link.}
original tag (in the sense of @racket[link-element]) for the link.
If the link is based on a cross-reference entry that has a
document-identifying string (see @racket[load-xref] and its
@racket[#:doc-id] argument), the document identifier is added as a
@tt{doc} query element, and a path to the target within the
document is added as a @tt{rel} query element.}
@defmethod[(set-external-root-url [url string?]) void?]{

View File

@ -25,7 +25,8 @@ by @racket[load-xref], @racket[#f] otherwise.}
(lambda (_tag) #f)]
[#:render% using-render% (implementation?/c render<%>)
(render-mixin render%)]
[#:root root-path (or/c path-string? false/c) #f])
[#:root root-path (or/c path-string? false/c) #f]
[#:doc-id doc-id-str (or/c path-string? false/c) #f])
xref?]{
Creates a cross-reference record given a list of functions,
@ -38,9 +39,11 @@ serialize-info]. The result of @racket[_source] can optionally be
another function, which is in turn responsible for returning a list of
@racket[_info]s. Finally, each @racket[_info] can be either serialized
information, a @racket[#f] to be ignored, or a value produced by
@racket[make-data+root] from which @racket[_data] part is used as
serialized information and the @racket[_root] part overrides
@racket[root-path] for deserialization.
@racket[make-data+root] or @racket[make-data+root+doc-id], from which
@racket[_data] part is used as serialized information, the
@racket[_root] part overrides @racket[root-path] for deserialization,
and the @racket[_doc-id] part (if any) overrides
@racket[doc-id-string] to identify the source document.
The @racket[demand-source] function can effectively add a new source
to @racket[sources] in response to a search for information on the
@ -60,8 +63,16 @@ but a @racket[make-data+root] result for any @racket[_info] supplies
an alternate path for deserialization of the @racket[_info]'s
@racket[_data].
If @racket[doc-id-str] is not @racket[#f], it identifies each
cross-reference entry as originating from @racket[doc-id-str]. This
identification is used when a rendering link to the cross-reference
entry as an external query; see the @racket[set-external-tag-path]
method of @racket[render-mixin].
Use @racket[load-collections-xref] from @racketmodname[setup/xref] to
get all cross-reference information for installed documentation.}
get all cross-reference information for installed documentation.
@history[#:changed "1.1" @elem{Added the @racket[#:doc-id] argument.}]}
@defproc[(xref-binding->definition-tag [xref xref?]
@ -213,3 +224,13 @@ the destination for the index link into the main document.}
A value constructed by @racket[make-data+root] can be returned by a
source procedure for @racket[load-xref] to specify a path used for
deserialization.}
@deftogether[(
@defproc[(data+root+doc-id? [v any/c]) boolean?]
@defproc[(make-data+root+doc-id [data any/c] [root (or/c #f path-string?)] [doc-id string?]) data+root+doc-id?]
)]{
Extends @racket[make-data+root+doc-id] to support an
document-identifying string (see @racket[load-xref]).
@history[#:added "1.1"]}

View File

@ -353,13 +353,13 @@
(when rp
(set-mobile-root-path! root rp))))))
(define/public (deserialize-info v ci #:root [root-path #f])
(define/public (deserialize-info v ci #:root [root-path #f] #:doc-id [doc-id #f])
(let ([root+ht (deserialize v)]
[in-ht (collect-info-ext-ht ci)])
(when root-path
(set-mobile-root-path! (car root+ht) root-path))
(for ([(k v) (cdr root+ht)])
(hash-set! in-ht k v))))
(hash-set! in-ht k (if doc-id (known-doc v doc-id) v)))))
(define/public (get-defined ci)
(hash-map (collect-info-ht ci) (lambda (k v) k)))

View File

@ -729,7 +729,8 @@
(collect-info-ext-ht ci))))
(lambda (k v)
(when (and (pair? k) (eq? 'index-entry (car k)))
(set! l (cons (cons (cadr k) v) l)))))
(let ([v (if (known-doc? v) (known-doc-v v) v)])
(set! l (cons (cons (cadr k) v) l))))))
(sort l entry<?))
(define (index-block)

View File

@ -37,31 +37,37 @@
(define ci (resolve-info-ci ri))
(define (try-ext)
(hash-ref (collect-info-ext-ht ci) key #f))
(values
(or (try-ext)
(and ((collect-info-ext-demand ci) key ci)
(try-ext)))
#t)]))))
(define v
(or (try-ext)
(and ((collect-info-ext-demand ci) key ci)
(try-ext))))
(if (known-doc? v)
(values (known-doc-v v) (known-doc-id v))
(values v #t))]))))
(define (resolve-get/ext? part ri key)
(resolve-get/ext?* part ri key #f))
(define-values (v ext-id) (resolve-get/ext-id* part ri key #f))
(values v (and ext-id #t)))
(define (resolve-get/ext?* part ri key search-key)
(let-values ([(v ext?) (resolve-get/where part ri key)])
(when ext?
(define (resolve-get/ext-id part ri key)
(resolve-get/ext-id* part ri key #f))
(define (resolve-get/ext-id* part ri key search-key)
(let-values ([(v ext-id) (resolve-get/where part ri key)])
(when ext-id
(hash-set! (resolve-info-undef ri) (tag-key key ri)
(if v 'found search-key)))
(values v ext?)))
(values v ext-id)))
(define (resolve-get part ri key)
(resolve-get* part ri key #f))
(define (resolve-get* part ri key search-key)
(let-values ([(v ext?) (resolve-get/ext?* part ri key search-key)])
(let-values ([(v ext-id) (resolve-get/ext-id* part ri key search-key)])
v))
(define (resolve-get/tentative part ri key)
(let-values ([(v ext?) (resolve-get/where part ri key)])
(let-values ([(v ext-id) (resolve-get/where part ri key)])
v))
(define (resolve-search search-key part ri key)
@ -215,7 +221,10 @@
[collected-info ([number (listof (or/c false/c exact-nonnegative-integer? string?))]
[parent (or/c false/c part?)]
[info any/c])])
[info any/c])]
[known-doc ([v any/c]
[id string?])])
(provide plain)
(define plain (make-style #f null))
@ -715,5 +724,6 @@
[resolve-get ((or/c part? false/c) resolve-info? info-key? . -> . any)]
[resolve-get/tentative ((or/c part? false/c) resolve-info? info-key? . -> . any)]
[resolve-get/ext? ((or/c part? false/c) resolve-info? info-key? . -> . any)]
[resolve-get/ext-id ((or/c part? false/c) resolve-info? info-key? . -> . any)]
[resolve-search (any/c (or/c part? false/c) resolve-info? info-key? . -> . any)]
[resolve-get-keys ((or/c part? false/c) resolve-info? (info-key? . -> . any/c) . -> . any/c)])

View File

@ -508,6 +508,19 @@
(anchor-name (dest-anchor dest))))))
"???"))
(define/private (dest->url-in-doc dest)
(and dest
(not (dest-redirect dest))
(format "~a~a~a"
(let-values ([(base name dir?) (split-path
(relative->path (dest-path dest)))])
name)
(if (dest-page? dest) "" "#")
(if (dest-page? dest)
""
(uri-unreserved-encode
(anchor-name (dest-anchor dest)))))))
(define/public (render-toc-view d ri)
(define has-sub-parts?
(pair? (part-parts d)))
@ -1239,15 +1252,15 @@
[(and (link-element? e) (not (current-no-links)))
(parameterize ([current-no-links #t])
(define indirect-link? (link-element-indirect? e))
(let-values ([(dest ext?)
(let-values ([(dest ext-id)
(if (and indirect-link?
external-tag-path)
(values #f #f)
(resolve-get/ext? part ri (link-element-tag e)))])
(resolve-get/ext-id part ri (link-element-tag e)))])
(if (or indirect-link? dest)
`((a [(href
`((a ([href
,(cond
[(and ext? external-root-url
[(and ext-id external-root-url
(let ([rel (find-relative-path
(find-doc-dir)
(relative->path (dest-path dest)))])
@ -1270,7 +1283,7 @@
(and (not (dest-page? dest))
(anchor-name (dest-anchor dest)))])))]
[(or indirect-link?
(and ext? external-tag-path))
(and ext-id external-tag-path))
;; Redirected to search:
(url->string*
(let ([u (string->url (or external-tag-path
@ -1279,16 +1292,20 @@
url
u
[query
(cons (cons 'tag (tag->query-string (link-element-tag e)))
(url-query u))])))]
(if (string? ext-id)
(list* (cons 'doc ext-id)
(cons 'rel (or (dest->url-in-doc dest) "???"))
(url-query u))
(cons (cons 'tag (tag->query-string (link-element-tag e)))
(url-query u)))])))]
[else
;; Normal link:
(dest->url dest)]))
(dest->url dest)])]
,@(attribs (if (or indirect-link?
(and ext? external-tag-path))
'((class "Sq"))
(and ext-id external-tag-path))
'([class "Sq"])
null))
[data-pltdoc "x"]]
[data-pltdoc "x"])
,@(if (empty-content? (element-content e))
(render-content (strip-aux (dest-title dest)) part ri)
(render-content (element-content e) part ri))))

View File

@ -1,6 +1,7 @@
#lang scheme/base
(require scribble/struct
(only-in scribble/core known-doc? known-doc-v)
scribble/base-render
scribble/search
(prefix-in html: scribble/html-render)
@ -17,7 +18,9 @@
xref-transfer-info
(struct-out entry)
make-data+root
data+root?)
data+root?
make-data+root+doc-id
data+root+doc-id?)
(define-struct entry
(words ; list of strings: main term, sub-term, etc.
@ -26,6 +29,7 @@
desc)) ; further info that depends on the kind of index entry
(define-struct data+root (data root))
(define-struct (data+root+doc-id data+root) (doc-id))
;; Private:
(define-struct xrefs (renderer ri))
@ -40,7 +44,8 @@
(define (load-xref sources
#:demand-source [demand-source (lambda (key) #f)]
#:render% [render% (html:render-mixin render%)]
#:root [root-path #f])
#:root [root-path #f]
#:doc-id [doc-id-str #f])
(let* ([renderer (new render% [dest-dir (find-system-path 'temp-dir)])]
[fp (send renderer traverse null null)]
[load-source (lambda (src ci)
@ -51,7 +56,11 @@
(when v
(define data (if (data+root? v) (data+root-data v) v))
(define root (if (data+root? v) (data+root-root v) root-path))
(send renderer deserialize-info data ci #:root root))))))]
(define doc-id (or (and (data+root+doc-id? v) (data+root+doc-id-doc-id v))
doc-id-str))
(send renderer deserialize-info data ci
#:root root
#:doc-id doc-id))))))]
[ci (send renderer collect null null fp
(lambda (key ci)
(define src (demand-source key))
@ -73,7 +82,10 @@
#:when
(and (pair? k)
(eq? (car k) 'index-entry)))
(make-entry (car v) (cadr v) (cadr k) (caddr v))))
(let ([v (if (known-doc? v)
(known-doc-v v)
v)])
(make-entry (car v) (cadr v) (cadr k) (caddr v)))))
;; dest-file can be #f, which will make it return a string holding the
;; resulting html
@ -151,8 +163,11 @@
(collect-info-ext-ht (resolve-info-ci (xrefs-ri xrefs)))
`(index-entry ,tag)
#f)])
(cond [v (make-entry (car v) (cadr v) (cadr tag) (caddr v))]
[(and (pair? tag) (eq? 'form (car tag)))
;; Try again with 'def:
(xref-tag->index-entry xrefs (cons 'def (cdr tag)))]
[else #f])))
(let ([v (if (known-doc? v)
(known-doc-v v)
v)])
(cond [v (make-entry (car v) (cadr v) (cadr tag) (caddr v))]
[(and (pair? tag) (eq? 'form (car tag)))
;; Try again with 'def:
(xref-tag->index-entry xrefs (cons 'def (cdr tag)))]
[else #f]))))