View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2006-2017, University of Amsterdam
    7                              VU University Amsterdam
    8    All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(pldoc_http,
   37          [ doc_enable/1,               % +Boolean
   38            doc_server/1,               % ?Port
   39            doc_server/2,               % ?Port, +Options
   40            doc_browser/0,
   41            doc_browser/1               % +What
   42          ]).   43:- use_module(library(pldoc)).   44:- if(exists_source(library(http/thread_httpd))).   45:- use_module(library(http/thread_httpd)).   46:- endif.   47:- use_module(library(http/http_parameters)).   48:- use_module(library(http/html_write)).   49:- use_module(library(http/mimetype)).   50:- use_module(library(dcg/basics)).   51:- use_module(library(http/http_dispatch)).   52:- use_module(library(http/http_hook)).   53:- use_module(library(http/http_path)).   54:- use_module(library(http/http_wrapper)).   55:- use_module(library(uri)).   56:- use_module(library(debug)).   57:- use_module(library(lists)).   58:- use_module(library(url)).   59:- use_module(library(socket)).   60:- use_module(library(option)).   61:- use_module(library(error)).   62:- use_module(library(www_browser)).   63:- use_module(pldoc(doc_process)).   64:- use_module(pldoc(doc_htmlsrc)).   65:- use_module(pldoc(doc_html)).   66:- use_module(pldoc(doc_index)).   67:- use_module(pldoc(doc_search)).   68:- use_module(pldoc(doc_man)).   69:- use_module(pldoc(doc_wiki)).   70:- use_module(pldoc(doc_util)).   71:- use_module(pldoc(doc_access)).   72:- use_module(pldoc(doc_pack)).   73:- use_module(pldoc(man_index)).

Documentation server

The module library(pldoc/http) provides an embedded HTTP documentation server that allows for browsing the documentation of all files loaded after library(pldoc) has been loaded. */

   82:- dynamic
   83    doc_server_port/1,
   84    doc_enabled/0.   85
   86http:location(pldoc, root(pldoc), []).
   87http:location(pldoc_man, pldoc(refman), []).
   88http:location(pldoc_pkg, pldoc(package), []).
   89http:location(pldoc_resource, Path, []) :-
   90    http_location_by_id(pldoc_resource, Path).
 doc_enable(+Boolean)
Actually activate the PlDoc server. Merely loading the server does not do so to avoid incidental loading in a user HTTP server making the documentation available.
   98doc_enable(true) :-
   99    (   doc_enabled
  100    ->  true
  101    ;   assertz(doc_enabled)
  102    ).
  103doc_enable(false) :-
  104    retractall(doc_enabled).
 doc_server(?Port) is det
 doc_server(?Port, +Options) is det
Start a documentation server in the current Prolog process. The server is started in a separate thread. Options are handed to http_server/2. In addition, the following options are recognised:
allow(HostOrIP)
Allow connections from HostOrIP. If HostOrIP is an atom it is matched to the hostname. It if starts with a ., suffix match is done, matching the domain. Finally it can be a term ip(A,B,C,D). See tcp_host_to_address/2 for details.
deny(HostOrIP)
See allow(HostOrIP).
edit(Bool)
Allow editing from localhost connections? Default: true.

The predicate doc_server/1 is defined as below, which provides a good default for development.

doc_server(Port) :-
        doc_server(Port,
                   [ allow(localhost)
                   ]).
See also
- doc_browser/1
  140doc_server(Port) :-
  141    doc_server(Port,
  142               [ allow(localhost),
  143                 allow(ip(127,0,0,1)) % Windows ip-->host often fails
  144               ]).
  145
  146doc_server(Port, _) :-
  147    doc_enable(true),
  148    catch(doc_current_server(Port), _, fail),
  149    !.
  150:- if(current_predicate(http_server/2)).  151doc_server(Port, Options) :-
  152    doc_enable(true),
  153    prepare_editor,
  154    host_access_options(Options, ServerOptions),
  155    http_absolute_location(pldoc('.'), Entry, []),
  156    merge_options(ServerOptions,
  157                  [ port(Port),
  158                    entry_page(Entry)
  159                  ], HTTPOptions),
  160    http_server(http_dispatch, HTTPOptions),
  161    assertz(doc_server_port(Port)).
  162:- endif.
 doc_current_server(-Port) is det
TCP/IP port of the documentation server. Fails if no server is running. Note that in the current infrastructure we can easily be embedded into another Prolog HTTP server. If we are not started from doc_server/2, we return the port of a running HTTP server.
Errors
- existence_error(http_server, pldoc)
To be done
- Trap destruction of the server.
  175doc_current_server(Port) :-
  176    (   doc_server_port(P)
  177    ->  Port = P
  178    ;   http_current_server(_:_, P)
  179    ->  Port = P
  180    ;   existence_error(http_server, pldoc)
  181    ).
  182
  183:- if(\+current_predicate(http_current_server/2)).  184http_current_server(_,_) :- fail.
  185:- endif.
 doc_browser is det
 doc_browser(+What) is semidet
Open user's default browser on the documentation server.
  192doc_browser :-
  193    doc_browser([]).
  194doc_browser(Spec) :-
  195    catch(doc_current_server(Port),
  196          error(existence_error(http_server, pldoc), _),
  197          doc_server(Port)),
  198    browser_url(Spec, Request),
  199    format(string(URL), 'http://localhost:~w~w', [Port, Request]),
  200    www_open_url(URL).
  201
  202browser_url([], Root) :-
  203    !,
  204    http_location_by_id(pldoc_root, Root).
  205browser_url(Name, URL) :-
  206    atom(Name),
  207    !,
  208    browser_url(Name/_, URL).
  209browser_url(Name//Arity, URL) :-
  210    must_be(atom, Name),
  211    integer(Arity),
  212    !,
  213    PredArity is Arity+2,
  214    browser_url(Name/PredArity, URL).
  215browser_url(Name/Arity, URL) :-
  216    !,
  217    must_be(atom, Name),
  218    (   man_object_property(Name/Arity, summary(_))
  219    ->  format(string(S), '~q/~w', [Name, Arity]),
  220        http_link_to_id(pldoc_man, [predicate=S], URL)
  221    ;   browser_url(_:Name/Arity, URL)
  222    ).
  223browser_url(Spec, URL) :-
  224    !,
  225    Spec = M:Name/Arity,
  226    doc_comment(Spec, _Pos, _Summary, _Comment),
  227    !,
  228    (   var(M)
  229    ->  format(string(S), '~q/~w', [Name, Arity])
  230    ;   format(string(S), '~q:~q/~w', [M, Name, Arity])
  231    ),
  232    http_link_to_id(pldoc_object, [object=S], URL).
 prepare_editor
Start XPCE as edit requests coming from the document server can only be handled if XPCE is running. This must load PceEmacs dynamically without triggering the undefined predicate detection. We cannot use conditional compilation as xpce could be installed later and may or may not be available depending on the --no-pce and availability of a GUI.
  243prepare_editor :-
  244    current_prolog_flag(editor, pce_emacs),
  245    exists_source(library(pce_emacs)),
  246    !,
  247    (   current_predicate(start_emacs/0)
  248    ->  true
  249    ;   use_module(library(pce_emacs), [start_emacs/0]),
  250        term_string(Goal, "start_emacs"),
  251        call(Goal)
  252    ).
  253prepare_editor.
  254
  255
  256                 /*******************************
  257                 *          USER REPLIES        *
  258                 *******************************/
  259
  260:- http_handler(pldoc(.),          pldoc_root,
  261                [ prefix,
  262                  authentication(pldoc(read)),
  263                  condition(doc_enabled)
  264                ]).  265:- http_handler(pldoc('index.html'), pldoc_index,   []).  266:- http_handler(pldoc(file),       pldoc_file,     []).  267:- http_handler(pldoc(place),      go_place,       []).  268:- http_handler(pldoc(edit),       pldoc_edit,
  269                [authentication(pldoc(edit))]).  270:- http_handler(pldoc(doc),        pldoc_doc,      [prefix]).  271:- http_handler(pldoc(man),        pldoc_man,      []).  272:- http_handler(pldoc(doc_for),    pldoc_object,   [id(pldoc_doc_for)]).  273:- http_handler(pldoc(search),     pldoc_search,   []).  274:- http_handler(pldoc('res/'),     pldoc_resource, [prefix]).
 pldoc_root(+Request)
Reply using the index-page of the Prolog working directory. There are various options for the start directory. For example we could also use the file or directory of the file that would be edited using edit/0.
  284pldoc_root(Request) :-
  285    http_parameters(Request,
  286                    [ empty(Empty, [ oneof([true,false]),
  287                                     default(false)
  288                                   ])
  289                    ]),
  290    pldoc_root(Request, Empty).
  291
  292pldoc_root(Request, false) :-
  293    http_location_by_id(pldoc_root, Root),
  294    memberchk(path(Path), Request),
  295    Root \== Path,
  296    !,
  297    existence_error(http_location, Path).
  298pldoc_root(_Request, false) :-
  299    working_directory(Dir0, Dir0),
  300    allowed_directory(Dir0),
  301    !,
  302    ensure_slash_end(Dir0, Dir1),
  303    doc_file_href(Dir1, Ref0),
  304    atom_concat(Ref0, 'index.html', Index),
  305    throw(http_reply(see_other(Index))).
  306pldoc_root(Request, _) :-
  307    pldoc_index(Request).
 pldoc_index(+Request)
HTTP handle for /index.html, providing an overall overview of the available documentation.
  315pldoc_index(_Request) :-
  316    reply_html_page(pldoc(index),
  317                    title('SWI-Prolog documentation'),
  318                    [ \doc_links('', []),
  319                       h1('SWI-Prolog documentation'),
  320                      \man_overview([])
  321                    ]).
 pldoc_file(+Request)
Hander for /file?file=File, providing documentation for File.
  328pldoc_file(Request) :-
  329    http_parameters(Request,
  330                    [ file(File, [])
  331                    ]),
  332    (   source_file(File)
  333    ->  true
  334    ;   throw(http_reply(forbidden(File)))
  335    ),
  336    doc_for_file(File, []).
 pldoc_edit(+Request)
HTTP handler that starts the user's default editor on the host running the server. This handler can only accessed if the browser connection originates from localhost. The call can edit files using the file attribute or a predicate if both name and arity is given and optionally module.
  346pldoc_edit(Request) :-
  347    http:authenticate(pldoc(edit), Request, _),
  348    http_parameters(Request,
  349                    [ file(File,
  350                           [ optional(true),
  351                             description('Name of the file to edit')
  352                           ]),
  353                      line(Line,
  354                           [ optional(true),
  355                             integer,
  356                             description('Line in the file')
  357                           ]),
  358                      name(Name,
  359                           [ optional(true),
  360                             description('Name of a Prolog predicate to edit')
  361                           ]),
  362                      arity(Arity,
  363                            [ integer,
  364                              optional(true),
  365                              description('Arity of a Prolog predicate to edit')
  366                            ]),
  367                      module(Module,
  368                             [ optional(true),
  369                               description('Name of a Prolog module to search for predicate')
  370                             ])
  371                    ]),
  372    (   atom(File)
  373    ->  allowed_file(File)
  374    ;   true
  375    ),
  376    (   atom(File), integer(Line)
  377    ->  Edit = file(File, line(Line))
  378    ;   atom(File)
  379    ->  Edit = file(File)
  380    ;   atom(Name), integer(Arity)
  381    ->  (   atom(Module)
  382        ->  Edit = (Module:Name/Arity)
  383        ;   Edit = (Name/Arity)
  384        )
  385    ),
  386    edit(Edit),
  387    format('Content-type: text/plain~n~n'),
  388    format('Started ~q~n', [edit(Edit)]).
  389pldoc_edit(_Request) :-
  390    http_location_by_id(pldoc_edit, Location),
  391    throw(http_reply(forbidden(Location))).
 go_place(+Request)
HTTP handler to handle the places menu.
  398go_place(Request) :-
  399    http_parameters(Request,
  400                    [ place(Place, [])
  401                    ]),
  402    places(Place).
  403
  404places(':packs:') :-
  405    !,
  406    http_link_to_id(pldoc_pack, [], HREF),
  407    throw(http_reply(moved(HREF))).
  408places(Dir0) :-
  409    expand_alias(Dir0, Dir),
  410    (   allowed_directory(Dir)
  411    ->  format(string(IndexFile), '~w/index.html', [Dir]),
  412        doc_file_href(IndexFile, HREF),
  413        throw(http_reply(moved(HREF)))
  414    ;   throw(http_reply(forbidden(Dir)))
  415    ).
 allowed_directory(+Dir) is semidet
True if we are allowed to produce and index for Dir.
  422allowed_directory(Dir) :-
  423    source_directory(Dir),
  424    !.
  425allowed_directory(Dir) :-
  426    working_directory(CWD, CWD),
  427    same_file(CWD, Dir).
  428allowed_directory(Dir) :-
  429    prolog:doc_directory(Dir).
 allowed_file(+File) is semidet
True if we are allowed to serve File. Currently means we have predicates loaded from File or the directory must be allowed.
  437allowed_file(File) :-
  438    source_file(_, File),
  439    !.
  440allowed_file(File) :-
  441    absolute_file_name(File, Canonical),
  442    file_directory_name(Canonical, Dir),
  443    allowed_directory(Dir).
 pldoc_resource(+Request)
Handler for /res/File, serving CSS, JS and image files.
  450pldoc_resource(Request) :-
  451    http_location_by_id(pldoc_resource, ResRoot),
  452    memberchk(path(Path), Request),
  453    atom_concat(ResRoot, File, Path),
  454    file(File, Local),
  455    http_reply_file(pldoc(Local), [], Request).
  456
  457file('pldoc.css',     'pldoc.css').
  458file('pllisting.css', 'pllisting.css').
  459file('pldoc.js',      'pldoc.js').
  460file('edit.png',      'edit.png').
  461file('editpred.png',  'editpred.png').
  462file('up.gif',        'up.gif').
  463file('source.png',    'source.png').
  464file('public.png',    'public.png').
  465file('private.png',   'private.png').
  466file('reload.png',    'reload.png').
  467file('favicon.ico',   'favicon.ico').
  468file('h1-bg.png',     'h1-bg.png').
  469file('h2-bg.png',     'h2-bg.png').
  470file('pub-bg.png',    'pub-bg.png').
  471file('priv-bg.png',   'priv-bg.png').
  472file('multi-bg.png',  'multi-bg.png').
 pldoc_doc(+Request)
Handler for /doc/Path

Reply documentation of a file. Path is the absolute path of the file for which to return the documentation. Extension is either none, the Prolog extension or the HTML extension.

Note that we reply with pldoc.css if the file basename is pldoc.css to allow for a relative link from any directory.

  486pldoc_doc(Request) :-
  487    memberchk(path(ReqPath), Request),
  488    http_location_by_id(pldoc_doc, Me),
  489    atom_concat(Me, AbsFile0, ReqPath),
  490    (   sub_atom(ReqPath, _, _, 0, /)
  491    ->  atom_concat(ReqPath, 'index.html', File),
  492        throw(http_reply(moved(File)))
  493    ;   clean_path(AbsFile0, AbsFile1),
  494        expand_alias(AbsFile1, AbsFile),
  495        is_absolute_file_name(AbsFile)
  496    ->  documentation(AbsFile, Request)
  497    ).
  498
  499documentation(Path, Request) :-
  500    file_base_name(Path, Base),
  501    file(_, Base),                         % serve pldoc.css, etc.
  502    !,
  503    http_reply_file(pldoc(Base), [], Request).
  504documentation(Path, Request) :-
  505    file_name_extension(_, Ext, Path),
  506    autolink_extension(Ext, image),
  507    http_reply_file(Path, [unsafe(true)], Request).
  508documentation(Path, Request) :-
  509    Index = '/index.html',
  510    sub_atom(Path, _, _, 0, Index),
  511    atom_concat(Dir, Index, Path),
  512    exists_directory(Dir),                 % Directory index
  513    !,
  514    (   allowed_directory(Dir)
  515    ->  edit_options(Request, EditOptions),
  516        doc_for_dir(Dir, EditOptions)
  517    ;   throw(http_reply(forbidden(Dir)))
  518    ).
  519documentation(File, Request) :-
  520    wiki_file(File, WikiFile),
  521    !,
  522    (   allowed_file(WikiFile)
  523    ->  true
  524    ;   throw(http_reply(forbidden(File)))
  525    ),
  526    edit_options(Request, Options),
  527    doc_for_wiki_file(WikiFile, Options).
  528documentation(Path, Request) :-
  529    pl_file(Path, File),
  530    !,
  531    (   allowed_file(File)
  532    ->  true
  533    ;   throw(http_reply(forbidden(File)))
  534    ),
  535    doc_reply_file(File, Request).
  536documentation(Path, _) :-
  537    throw(http_reply(not_found(Path))).
  538
  539:- public
  540    doc_reply_file/2.  541
  542doc_reply_file(File, Request) :-
  543    http_parameters(Request,
  544                    [ public_only(Public),
  545                      reload(Reload),
  546                      show(Show),
  547                      format_comments(FormatComments)
  548                    ],
  549                    [ attribute_declarations(param)
  550                    ]),
  551    (   exists_file(File)
  552    ->  true
  553    ;   throw(http_reply(not_found(File)))
  554    ),
  555    (   Reload == true,
  556        source_file(File)
  557    ->  load_files(File, [if(changed), imports([])])
  558    ;   true
  559    ),
  560    edit_options(Request, EditOptions),
  561    (   Show == src
  562    ->  format('Content-type: text/html~n~n', []),
  563        source_to_html(File, stream(current_output),
  564                       [ skin(src_skin(Request, Show, FormatComments)),
  565                         format_comments(FormatComments)
  566                       ])
  567    ;   Show == raw
  568    ->  http_reply_file(File,
  569                        [ unsafe(true), % is already validated
  570                          mime_type(text/plain)
  571                        ], Request)
  572    ;   doc_for_file(File,
  573                     [ public_only(Public),
  574                       source_link(true)
  575                     | EditOptions
  576                     ])
  577    ).
  578
  579
  580:- public src_skin/5.                   % called through source_to_html/3.
  581
  582src_skin(Request, _Show, FormatComments, header, Out) :-
  583    memberchk(request_uri(ReqURI), Request),
  584    negate(FormatComments, AltFormatComments),
  585    replace_parameters(ReqURI, [show(raw)], RawLink),
  586    replace_parameters(ReqURI, [format_comments(AltFormatComments)], CmtLink),
  587    phrase(html(div(class(src_formats),
  588                    [ 'View source with ',
  589                      a(href(CmtLink), \alt_view(AltFormatComments)),
  590                      ' or as ',
  591                      a(href(RawLink), raw)
  592                    ])), Tokens),
  593    print_html(Out, Tokens).
  594
  595alt_view(true) -->
  596    html('formatted comments').
  597alt_view(false) -->
  598    html('raw comments').
  599
  600negate(true, false).
  601negate(false, true).
  602
  603replace_parameters(ReqURI, Extra, URI) :-
  604    uri_components(ReqURI, C0),
  605    uri_data(search, C0, Search0),
  606    (   var(Search0)
  607    ->  uri_query_components(Search, Extra)
  608    ;   uri_query_components(Search0, Form0),
  609        merge_options(Extra, Form0, Form),
  610        uri_query_components(Search, Form)
  611    ),
  612    uri_data(search, C0, Search, C),
  613    uri_components(URI, C).
 edit_options(+Request, -Options) is det
Return edit(true) in Options if the connection is from the localhost.
  621edit_options(Request, [edit(true)]) :-
  622    catch(http:authenticate(pldoc(edit), Request, _), _, fail),
  623    !.
  624edit_options(_, []).
 pl_file(+File, -PlFile) is semidet
  629pl_file(File, PlFile) :-
  630    file_name_extension(Base, html, File),
  631    !,
  632    absolute_file_name(Base,
  633                       PlFile,
  634                       [ file_errors(fail),
  635                         file_type(prolog),
  636                         access(read)
  637                       ]).
  638pl_file(File, File).
 wiki_file(+File, -TxtFile) is semidet
True if TxtFile is an existing file that must be served as wiki file.
  645wiki_file(File, TxtFile) :-
  646    file_name_extension(_, Ext, File),
  647    wiki_file_extension(Ext),
  648    !,
  649    TxtFile = File.
  650wiki_file(File, TxtFile) :-
  651    file_base_name(File, Base),
  652    autolink_file(Base, wiki),
  653    !,
  654    TxtFile = File.
  655wiki_file(File, TxtFile) :-
  656    file_name_extension(Base, html, File),
  657    wiki_file_extension(Ext),
  658    file_name_extension(Base, Ext, TxtFile),
  659    access_file(TxtFile, read).
  660
  661wiki_file_extension(md).
  662wiki_file_extension(txt).
 clean_path(+AfterDoc, -AbsPath)
Restore the path, Notably deals Windows issues
  669clean_path(Path0, Path) :-
  670    current_prolog_flag(windows, true),
  671    sub_atom(Path0, 2, _, _, :),
  672    !,
  673    sub_atom(Path0, 1, _, 0, Path).
  674clean_path(Path, Path).
 pldoc_man(+Request)
Handler for /man, offering one of the parameters:
predicate=PI
providing documentation from the manual on the predicate PI.
function=PI
providing documentation from the manual on the function PI.
CAPI=F
providing documentation from the manual on the C-function F.
  688pldoc_man(Request) :-
  689    http_parameters(Request,
  690                    [ predicate(PI, [optional(true)]),
  691                      function(Fun, [optional(true)]),
  692                      'CAPI'(F,     [optional(true)]),
  693                      section(Sec,  [optional(true)])
  694                    ]),
  695    (   ground(PI)
  696    ->  atom_pi(PI, Obj)
  697    ;   ground(Fun)
  698    ->  atomic_list_concat([Name,ArityAtom], /, Fun),
  699        atom_number(ArityAtom, Arity),
  700        Obj = f(Name/Arity)
  701    ;   ground(F)
  702    ->  Obj = c(F)
  703    ;   ground(Sec)
  704    ->  atom_concat('sec:', Sec, SecID),
  705        Obj = section(SecID)
  706    ),
  707    man_title(Obj, Title),
  708    reply_html_page(
  709        pldoc(object(Obj)),
  710        title(Title),
  711        \man_page(Obj, [])).
  712
  713man_title(f(Obj), Title) :-
  714    !,
  715    format(atom(Title), 'SWI-Prolog -- function ~w', [Obj]).
  716man_title(c(Obj), Title) :-
  717    !,
  718    format(atom(Title), 'SWI-Prolog -- API-function ~w', [Obj]).
  719man_title(section(Id), Title) :-
  720    !,
  721    (   manual_object(section(_L, _N, Id, _F),
  722                      STitle, _File, _Class, _Offset)
  723    ->  true
  724    ;   STitle = 'Manual'
  725    ),
  726    format(atom(Title), 'SWI-Prolog -- ~w', [STitle]).
  727man_title(Obj, Title) :-
  728    copy_term(Obj, Copy),
  729    numbervars(Copy, 0, _, [singletons(true)]),
  730    format(atom(Title), 'SWI-Prolog -- ~p', [Copy]).
 pldoc_object(+Request)
Handler for /doc_for?object=Term, Provide documentation for the given term.
  737pldoc_object(Request) :-
  738    http_parameters(Request,
  739                    [ object(Atom, []),
  740                      header(Header, [default(true)])
  741                    ]),
  742    (   catch(atom_to_term(Atom, Obj, _), error(_,_), fail)
  743    ->  true
  744    ;   atom_to_object(Atom, Obj)
  745    ),
  746    (   prolog:doc_object_title(Obj, Title)
  747    ->  true
  748    ;   Title = Atom
  749    ),
  750    edit_options(Request, EditOptions),
  751    reply_html_page(
  752        pldoc(object(Obj)),
  753        title(Title),
  754        \object_page(Obj, [header(Header)|EditOptions])).
 pldoc_search(+Request)
Search the collected PlDoc comments and Prolog manual.
  761pldoc_search(Request) :-
  762    http_parameters(Request,
  763                    [ for(For,
  764                          [ optional(true),
  765                            description('String to search for')
  766                          ]),
  767                      page(Page,
  768                           [ integer,
  769                             default(1),
  770                             description('Page of search results to view')
  771                           ]),
  772                      in(In,
  773                         [ oneof([all,app,noapp,man,lib,pack,wiki]),
  774                           default(all),
  775                           description('Search everying, application only or manual only')
  776                         ]),
  777                      match(Match,
  778                            [ oneof([name,summary]),
  779                              default(summary),
  780                              description('Match only the name or also the summary')
  781                            ]),
  782                      resultFormat(Format,
  783                                   [ oneof(long,summary),
  784                                     default(summary),
  785                                     description('Return full documentation or summary-lines')
  786                                   ])
  787                    ]),
  788    edit_options(Request, EditOptions),
  789    format(string(Title), 'Prolog search -- ~w', [For]),
  790    reply_html_page(pldoc(search(For)),
  791                    title(Title),
  792                    \search_reply(For,
  793                                  [ resultFormat(Format),
  794                                    search_in(In),
  795                                    search_match(Match),
  796                                    page(Page)
  797                                  | EditOptions
  798                                  ])).
  799
  800
  801                 /*******************************
  802                 *     HTTP PARAMETER TYPES     *
  803                 *******************************/
  804
  805:- public
  806    param/2.                        % used in pack documentation server
  807
  808param(public_only,
  809      [ boolean,
  810        default(true),
  811        description('If true, hide private predicates')
  812      ]).
  813param(reload,
  814      [ boolean,
  815        default(false),
  816        description('Reload the file and its documentation')
  817      ]).
  818param(show,
  819      [ oneof([doc,src,raw]),
  820        default(doc),
  821        description('How to show the file')
  822      ]).
  823param(format_comments,
  824      [ boolean,
  825        default(true),
  826        description('If true, use PlDoc for rendering structured comments')
  827      ])