View source with formatted 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:- use_module(library(http/thread_httpd)).   45:- use_module(library(http/http_parameters)).   46:- use_module(library(http/html_write)).   47:- use_module(library(http/mimetype)).   48:- use_module(library(dcg/basics)).   49:- use_module(library(http/http_dispatch)).   50:- use_module(library(http/http_hook)).   51:- use_module(library(http/http_path)).   52:- use_module(library(http/http_wrapper)).   53:- use_module(library(uri)).   54:- use_module(library(debug)).   55:- use_module(library(lists)).   56:- use_module(library(url)).   57:- use_module(library(socket)).   58:- use_module(library(option)).   59:- use_module(library(error)).   60:- use_module(library(www_browser)).   61:- use_module(pldoc(doc_process)).   62:- use_module(pldoc(doc_htmlsrc)).   63:- use_module(pldoc(doc_html)).   64:- use_module(pldoc(doc_index)).   65:- use_module(pldoc(doc_search)).   66:- use_module(pldoc(doc_man)).   67:- use_module(pldoc(doc_wiki)).   68:- use_module(pldoc(doc_util)).   69:- use_module(pldoc(doc_access)).   70:- use_module(pldoc(doc_pack)).   71
   72/** <module> Documentation server
   73
   74The module library(pldoc/http) provides an   embedded HTTP documentation
   75server that allows for browsing the   documentation  of all files loaded
   76_after_ library(pldoc) has been loaded.
   77*/
   78
   79:- dynamic
   80    doc_server_port/1,
   81    doc_enabled/0.   82
   83http:location(pldoc, root(pldoc), []).
   84http:location(pldoc_man, pldoc(refman), []).
   85http:location(pldoc_pkg, pldoc(package), []).
   86http:location(pldoc_resource, Path, []) :-
   87    http_location_by_id(pldoc_resource, Path).
   88
   89%!  doc_enable(+Boolean)
   90%
   91%   Actually activate the PlDoc server. Merely   loading the server does
   92%   not do so to avoid incidental loading   in a user HTTP server making
   93%   the documentation available.
   94
   95doc_enable(true) :-
   96    (   doc_enabled
   97    ->  true
   98    ;   assertz(doc_enabled)
   99    ).
  100doc_enable(false) :-
  101    retractall(doc_enabled).
  102
  103%!  doc_server(?Port) is det.
  104%!  doc_server(?Port, +Options) is det.
  105%
  106%   Start a documentation server in the  current Prolog process. The
  107%   server is started in a seperate   thread.  Options are handed to
  108%   http_server/2.  In  addition,   the    following   options   are
  109%   recognised:
  110%
  111%           * allow(HostOrIP)
  112%           Allow connections from HostOrIP.  If HostOrIP is an atom
  113%           it is matched to the hostname.  It if starts with a .,
  114%           suffix match is done, matching the domain.  Finally it
  115%           can be a term ip(A,B,C,D). See tcp_host_to_address/2 for
  116%           details.
  117%
  118%           * deny(HostOrIP)
  119%           See allow(HostOrIP).
  120%
  121%           * edit(Bool)
  122%           Allow editing from localhost connections? Default:
  123%           =true=.
  124%
  125%   The predicate doc_server/1 is defined as below, which provides a
  126%   good default for development.
  127%
  128%   ==
  129%   doc_server(Port) :-
  130%           doc_server(Port,
  131%                      [ allow(localhost)
  132%                      ]).
  133%   ==
  134%
  135%   @see    doc_browser/1
  136
  137doc_server(Port) :-
  138    doc_server(Port,
  139               [ allow(localhost),
  140                 allow(ip(127,0,0,1)) % Windows ip-->host often fails
  141               ]).
  142
  143doc_server(Port, _) :-
  144    doc_enable(true),
  145    catch(doc_current_server(Port), _, fail),
  146    !.
  147doc_server(Port, Options) :-
  148    doc_enable(true),
  149    prepare_editor,
  150    host_access_options(Options, ServerOptions),
  151    http_absolute_location(pldoc('.'), Entry, []),
  152    merge_options(ServerOptions,
  153                  [ port(Port),
  154                    entry_page(Entry)
  155                  ], HTTPOptions),
  156    http_server(http_dispatch, HTTPOptions),
  157    assertz(doc_server_port(Port)).
  158
  159%!  doc_current_server(-Port) is det.
  160%
  161%   TCP/IP port of the documentation server.   Fails if no server is
  162%   running. Note that in the current   infrastructure we can easily
  163%   be embedded into another  Prolog  HTTP   server.  If  we are not
  164%   started from doc_server/2, we  return  the   port  of  a running
  165%   HTTP server.
  166%
  167%   @tbd    Trap destruction of the server.
  168%   @error  existence_error(http_server, pldoc)
  169
  170doc_current_server(Port) :-
  171    (   doc_server_port(P)
  172    ->  Port = P
  173    ;   http_current_server(_:_, P)
  174    ->  Port = P
  175    ;   existence_error(http_server, pldoc)
  176    ).
  177
  178%!  doc_browser is det.
  179%!  doc_browser(+What) is semidet.
  180%
  181%   Open user's default browser on the documentation server.
  182
  183doc_browser :-
  184    doc_browser([]).
  185doc_browser(Spec) :-
  186    catch(doc_current_server(Port),
  187          error(existence_error(http_server, pldoc), _),
  188          doc_server(Port)),
  189    browser_url(Spec, Request),
  190    format(string(URL), 'http://localhost:~w~w', [Port, Request]),
  191    www_open_url(URL).
  192
  193browser_url([], Root) :-
  194    !,
  195    http_location_by_id(pldoc_root, Root).
  196browser_url(Name, URL) :-
  197    atom(Name),
  198    !,
  199    browser_url(Name/_, URL).
  200browser_url(Name//Arity, URL) :-
  201    must_be(atom, Name),
  202    integer(Arity),
  203    !,
  204    PredArity is Arity+2,
  205    browser_url(Name/PredArity, URL).
  206browser_url(Name/Arity, URL) :-
  207    !,
  208    must_be(atom, Name),
  209    (   predicate(Name, Arity, _, _, _)
  210    ->  format(string(S), '~q/~w', [Name, Arity]),
  211        http_link_to_id(pldoc_man, [predicate=S], URL)
  212    ;   browser_url(_:Name/Arity, URL)
  213    ).
  214browser_url(Spec, URL) :-
  215    !,
  216    Spec = M:Name/Arity,
  217    doc_comment(Spec, _Pos, _Summary, _Comment),
  218    !,
  219    (   var(M)
  220    ->  format(string(S), '~q/~w', [Name, Arity])
  221    ;   format(string(S), '~q:~q/~w', [M, Name, Arity])
  222    ),
  223    http_link_to_id(pldoc_object, [object=S], URL).
  224
  225%!  prepare_editor
  226%
  227%   Start XPCE as edit requests comming from the document server can
  228%   only be handled if XPCE is running.
  229
  230prepare_editor :-
  231    current_prolog_flag(editor, pce_emacs),
  232    !,
  233    start_emacs.
  234prepare_editor.
  235
  236
  237                 /*******************************
  238                 *          USER REPLIES        *
  239                 *******************************/
  240
  241:- http_handler(pldoc(.),          pldoc_root,
  242                [ prefix,
  243                  authentication(pldoc(read)),
  244                  condition(doc_enabled)
  245                ]).  246:- http_handler(pldoc('index.html'), pldoc_index,   []).  247:- http_handler(pldoc(file),       pldoc_file,     []).  248:- http_handler(pldoc(place),      go_place,       []).  249:- http_handler(pldoc(edit),       pldoc_edit,
  250                [authentication(pldoc(edit))]).  251:- http_handler(pldoc(doc),        pldoc_doc,      [prefix]).  252:- http_handler(pldoc(man),        pldoc_man,      []).  253:- http_handler(pldoc(doc_for),    pldoc_object,   [id(pldoc_doc_for)]).  254:- http_handler(pldoc(search),     pldoc_search,   []).  255:- http_handler(pldoc('res/'),     pldoc_resource, [prefix]).  256
  257
  258%!  pldoc_root(+Request)
  259%
  260%   Reply using the index-page  of   the  Prolog  working directory.
  261%   There are various options for the   start directory. For example
  262%   we could also use the file or   directory of the file that would
  263%   be edited using edit/0.
  264
  265pldoc_root(Request) :-
  266    http_parameters(Request,
  267                    [ empty(Empty, [ oneof([true,false]),
  268                                     default(false)
  269                                   ])
  270                    ]),
  271    pldoc_root(Request, Empty).
  272
  273pldoc_root(Request, false) :-
  274    http_location_by_id(pldoc_root, Root),
  275    memberchk(path(Path), Request),
  276    Root \== Path,
  277    !,
  278    existence_error(http_location, Path).
  279pldoc_root(_Request, false) :-
  280    working_directory(Dir0, Dir0),
  281    allowed_directory(Dir0),
  282    !,
  283    ensure_slash_end(Dir0, Dir1),
  284    doc_file_href(Dir1, Ref0),
  285    atom_concat(Ref0, 'index.html', Index),
  286    throw(http_reply(see_other(Index))).
  287pldoc_root(Request, _) :-
  288    pldoc_index(Request).
  289
  290
  291%!  pldoc_index(+Request)
  292%
  293%   HTTP handle for /index.html, providing an overall overview
  294%   of the available documentation.
  295
  296pldoc_index(_Request) :-
  297    reply_html_page(pldoc(index),
  298                    title('SWI-Prolog documentation'),
  299                    [ \doc_links('', []),
  300                       h1('SWI-Prolog documentation'),
  301                      \man_overview([])
  302                    ]).
  303
  304
  305%!  pldoc_file(+Request)
  306%
  307%   Hander for /file?file=File, providing documentation for File.
  308
  309pldoc_file(Request) :-
  310    http_parameters(Request,
  311                    [ file(File, [])
  312                    ]),
  313    (   source_file(File)
  314    ->  true
  315    ;   throw(http_reply(forbidden(File)))
  316    ),
  317    doc_for_file(File, []).
  318
  319%!  pldoc_edit(+Request)
  320%
  321%   HTTP handler that starts the user's   default editor on the host
  322%   running the server. This  handler  can   only  accessed  if  the
  323%   browser connection originates from  =localhost=.   The  call can
  324%   edit files using the =file=  attribute   or  a predicate if both
  325%   =name= and =arity= is given and optionally =module=.
  326
  327pldoc_edit(Request) :-
  328    http:authenticate(pldoc(edit), Request, _),
  329    http_parameters(Request,
  330                    [ file(File,
  331                           [ optional(true),
  332                             description('Name of the file to edit')
  333                           ]),
  334                      line(Line,
  335                           [ optional(true),
  336                             integer,
  337                             description('Line in the file')
  338                           ]),
  339                      name(Name,
  340                           [ optional(true),
  341                             description('Name of a Prolog predicate to edit')
  342                           ]),
  343                      arity(Arity,
  344                            [ integer,
  345                              optional(true),
  346                              description('Arity of a Prolog predicate to edit')
  347                            ]),
  348                      module(Module,
  349                             [ optional(true),
  350                               description('Name of a Prolog module to search for predicate')
  351                             ])
  352                    ]),
  353    (   atom(File)
  354    ->  allowed_file(File)
  355    ;   true
  356    ),
  357    (   atom(File), integer(Line)
  358    ->  Edit = file(File, line(Line))
  359    ;   atom(File)
  360    ->  Edit = file(File)
  361    ;   atom(Name), integer(Arity)
  362    ->  (   atom(Module)
  363        ->  Edit = (Module:Name/Arity)
  364        ;   Edit = (Name/Arity)
  365        )
  366    ),
  367    edit(Edit),
  368    format('Content-type: text/plain~n~n'),
  369    format('Started ~q~n', [edit(Edit)]).
  370pldoc_edit(_Request) :-
  371    http_location_by_id(pldoc_edit, Location),
  372    throw(http_reply(forbidden(Location))).
  373
  374
  375%!  go_place(+Request)
  376%
  377%   HTTP handler to handle the places menu.
  378
  379go_place(Request) :-
  380    http_parameters(Request,
  381                    [ place(Place, [])
  382                    ]),
  383    places(Place).
  384
  385places(':packs:') :-
  386    !,
  387    http_link_to_id(pldoc_pack, [], HREF),
  388    throw(http_reply(moved(HREF))).
  389places(Dir0) :-
  390    expand_alias(Dir0, Dir),
  391    (   allowed_directory(Dir)
  392    ->  format(string(IndexFile), '~w/index.html', [Dir]),
  393        doc_file_href(IndexFile, HREF),
  394        throw(http_reply(moved(HREF)))
  395    ;   throw(http_reply(forbidden(Dir)))
  396    ).
  397
  398
  399%!  allowed_directory(+Dir) is semidet.
  400%
  401%   True if we are allowed to produce and index for Dir.
  402
  403allowed_directory(Dir) :-
  404    source_directory(Dir),
  405    !.
  406allowed_directory(Dir) :-
  407    working_directory(CWD, CWD),
  408    same_file(CWD, Dir).
  409allowed_directory(Dir) :-
  410    prolog:doc_directory(Dir).
  411
  412
  413%!  allowed_file(+File) is semidet.
  414%
  415%   True if we are allowed to serve   File.  Currently means we have
  416%   predicates loaded from File or the directory must be allowed.
  417
  418allowed_file(File) :-
  419    source_file(_, File),
  420    !.
  421allowed_file(File) :-
  422    absolute_file_name(File, Canonical),
  423    file_directory_name(Canonical, Dir),
  424    allowed_directory(Dir).
  425
  426
  427%!  pldoc_resource(+Request)
  428%
  429%   Handler for /res/File, serving CSS, JS and image files.
  430
  431pldoc_resource(Request) :-
  432    http_location_by_id(pldoc_resource, ResRoot),
  433    memberchk(path(Path), Request),
  434    atom_concat(ResRoot, File, Path),
  435    file(File, Local),
  436    http_reply_file(pldoc(Local), [], Request).
  437
  438file('pldoc.css',     'pldoc.css').
  439file('pllisting.css', 'pllisting.css').
  440file('pldoc.js',      'pldoc.js').
  441file('edit.png',      'edit.png').
  442file('editpred.png',  'editpred.png').
  443file('up.gif',        'up.gif').
  444file('source.png',    'source.png').
  445file('public.png',    'public.png').
  446file('private.png',   'private.png').
  447file('reload.png',    'reload.png').
  448file('favicon.ico',   'favicon.ico').
  449file('h1-bg.png',     'h1-bg.png').
  450file('h2-bg.png',     'h2-bg.png').
  451file('pub-bg.png',    'pub-bg.png').
  452file('priv-bg.png',   'priv-bg.png').
  453file('multi-bg.png',  'multi-bg.png').
  454
  455
  456%!  pldoc_doc(+Request)
  457%
  458%   Handler for /doc/Path
  459%
  460%   Reply documentation of a file. Path is  the absolute path of the
  461%   file for which to return the  documentation. Extension is either
  462%   none, the Prolog extension or the HTML extension.
  463%
  464%   Note that we reply  with  pldoc.css   if  the  file  basename is
  465%   pldoc.css to allow for a relative link from any directory.
  466
  467pldoc_doc(Request) :-
  468    memberchk(path(ReqPath), Request),
  469    http_location_by_id(pldoc_doc, Me),
  470    atom_concat(Me, AbsFile0, ReqPath),
  471    (   sub_atom(ReqPath, _, _, 0, /)
  472    ->  atom_concat(ReqPath, 'index.html', File),
  473        throw(http_reply(moved(File)))
  474    ;   clean_path(AbsFile0, AbsFile1),
  475        expand_alias(AbsFile1, AbsFile),
  476        is_absolute_file_name(AbsFile)
  477    ->  documentation(AbsFile, Request)
  478    ).
  479
  480documentation(Path, Request) :-
  481    file_base_name(Path, Base),
  482    file(_, Base),                         % serve pldoc.css, etc.
  483    !,
  484    http_reply_file(pldoc(Base), [], Request).
  485documentation(Path, Request) :-
  486    file_name_extension(_, Ext, Path),
  487    autolink_extension(Ext, image),
  488    http_reply_file(Path, [unsafe(true)], Request).
  489documentation(Path, Request) :-
  490    Index = '/index.html',
  491    sub_atom(Path, _, _, 0, Index),
  492    atom_concat(Dir, Index, Path),
  493    exists_directory(Dir),                 % Directory index
  494    !,
  495    (   allowed_directory(Dir)
  496    ->  edit_options(Request, EditOptions),
  497        doc_for_dir(Dir, EditOptions)
  498    ;   throw(http_reply(forbidden(Dir)))
  499    ).
  500documentation(File, Request) :-
  501    wiki_file(File, WikiFile),
  502    !,
  503    (   allowed_file(WikiFile)
  504    ->  true
  505    ;   throw(http_reply(forbidden(File)))
  506    ),
  507    edit_options(Request, Options),
  508    doc_for_wiki_file(WikiFile, Options).
  509documentation(Path, Request) :-
  510    pl_file(Path, File),
  511    !,
  512    (   allowed_file(File)
  513    ->  true
  514    ;   throw(http_reply(forbidden(File)))
  515    ),
  516    doc_reply_file(File, Request).
  517documentation(Path, _) :-
  518    throw(http_reply(not_found(Path))).
  519
  520:- public
  521    doc_reply_file/2.  522
  523doc_reply_file(File, Request) :-
  524    http_parameters(Request,
  525                    [ public_only(Public),
  526                      reload(Reload),
  527                      show(Show),
  528                      format_comments(FormatComments)
  529                    ],
  530                    [ attribute_declarations(param)
  531                    ]),
  532    (   exists_file(File)
  533    ->  true
  534    ;   throw(http_reply(not_found(File)))
  535    ),
  536    (   Reload == true,
  537        source_file(File)
  538    ->  load_files(File, [if(changed), imports([])])
  539    ;   true
  540    ),
  541    edit_options(Request, EditOptions),
  542    (   Show == src
  543    ->  format('Content-type: text/html~n~n', []),
  544        source_to_html(File, stream(current_output),
  545                       [ skin(src_skin(Request, Show, FormatComments)),
  546                         format_comments(FormatComments)
  547                       ])
  548    ;   Show == raw
  549    ->  http_reply_file(File,
  550                        [ unsafe(true), % is already validated
  551                          mime_type(text/plain)
  552                        ], Request)
  553    ;   doc_for_file(File,
  554                     [ public_only(Public),
  555                       source_link(true)
  556                     | EditOptions
  557                     ])
  558    ).
  559
  560
  561:- public src_skin/5.                   % called through source_to_html/3.
  562
  563src_skin(Request, _Show, FormatComments, header, Out) :-
  564    memberchk(request_uri(ReqURI), Request),
  565    negate(FormatComments, AltFormatComments),
  566    replace_parameters(ReqURI, [show(raw)], RawLink),
  567    replace_parameters(ReqURI, [format_comments(AltFormatComments)], CmtLink),
  568    phrase(html(div(class(src_formats),
  569                    [ 'View source with ',
  570                      a(href(CmtLink), \alt_view(AltFormatComments)),
  571                      ' or as ',
  572                      a(href(RawLink), raw)
  573                    ])), Tokens),
  574    print_html(Out, Tokens).
  575
  576alt_view(true) -->
  577    html('formatted comments').
  578alt_view(false) -->
  579    html('raw comments').
  580
  581negate(true, false).
  582negate(false, true).
  583
  584replace_parameters(ReqURI, Extra, URI) :-
  585    uri_components(ReqURI, C0),
  586    uri_data(search, C0, Search0),
  587    (   var(Search0)
  588    ->  uri_query_components(Search, Extra)
  589    ;   uri_query_components(Search0, Form0),
  590        merge_options(Extra, Form0, Form),
  591        uri_query_components(Search, Form)
  592    ),
  593    uri_data(search, C0, Search, C),
  594    uri_components(URI, C).
  595
  596
  597%!  edit_options(+Request, -Options) is det.
  598%
  599%   Return edit(true) in Options  if  the   connection  is  from the
  600%   localhost.
  601
  602edit_options(Request, [edit(true)]) :-
  603    catch(http:authenticate(pldoc(edit), Request, _), _, fail),
  604    !.
  605edit_options(_, []).
  606
  607
  608%!  pl_file(+File, -PlFile) is semidet.
  609
  610pl_file(File, PlFile) :-
  611    file_name_extension(Base, html, File),
  612    !,
  613    absolute_file_name(Base,
  614                       PlFile,
  615                       [ file_errors(fail),
  616                         file_type(prolog),
  617                         access(read)
  618                       ]).
  619pl_file(File, File).
  620
  621%!  wiki_file(+File, -TxtFile) is semidet.
  622%
  623%   True if TxtFile is an existing file  that must be served as wiki
  624%   file.
  625
  626wiki_file(File, TxtFile) :-
  627    file_name_extension(_, Ext, File),
  628    wiki_file_extension(Ext),
  629    !,
  630    TxtFile = File.
  631wiki_file(File, TxtFile) :-
  632    file_base_name(File, Base),
  633    autolink_file(Base, wiki),
  634    !,
  635    TxtFile = File.
  636wiki_file(File, TxtFile) :-
  637    file_name_extension(Base, html, File),
  638    wiki_file_extension(Ext),
  639    file_name_extension(Base, Ext, TxtFile),
  640    access_file(TxtFile, read).
  641
  642wiki_file_extension(md).
  643wiki_file_extension(txt).
  644
  645
  646%!  clean_path(+AfterDoc, -AbsPath)
  647%
  648%   Restore the path, Notably deals Windows issues
  649
  650clean_path(Path0, Path) :-
  651    current_prolog_flag(windows, true),
  652    sub_atom(Path0, 2, _, _, :),
  653    !,
  654    sub_atom(Path0, 1, _, 0, Path).
  655clean_path(Path, Path).
  656
  657
  658%!  pldoc_man(+Request)
  659%
  660%   Handler for /man, offering one of the parameters:
  661%
  662%       * predicate=PI
  663%       providing documentation from the manual on the predicate PI.
  664%       * function=PI
  665%       providing documentation from the manual on the function PI.
  666%       * 'CAPI'=F
  667%       providing documentation from the manual on the C-function F.
  668
  669pldoc_man(Request) :-
  670    http_parameters(Request,
  671                    [ predicate(PI, [optional(true)]),
  672                      function(Fun, [optional(true)]),
  673                      'CAPI'(F,     [optional(true)]),
  674                      section(Sec,  [optional(true)])
  675                    ]),
  676    (   ground(PI)
  677    ->  atom_pi(PI, Obj)
  678    ;   ground(Fun)
  679    ->  atomic_list_concat([Name,ArityAtom], /, Fun),
  680        atom_number(ArityAtom, Arity),
  681        Obj = f(Name/Arity)
  682    ;   ground(F)
  683    ->  Obj = c(F)
  684    ;   ground(Sec)
  685    ->  atom_concat('sec:', Sec, SecID),
  686        Obj = section(SecID)
  687    ),
  688    man_title(Obj, Title),
  689    reply_html_page(
  690        pldoc(object(Obj)),
  691        title(Title),
  692        \man_page(Obj, [])).
  693
  694man_title(f(Obj), Title) :-
  695    !,
  696    format(atom(Title), 'SWI-Prolog -- function ~w', [Obj]).
  697man_title(c(Obj), Title) :-
  698    !,
  699    format(atom(Title), 'SWI-Prolog -- API-function ~w', [Obj]).
  700man_title(section(_Id), Title) :-
  701    !,
  702    format(atom(Title), 'SWI-Prolog -- Manual', []).
  703man_title(Obj, Title) :-
  704    format(atom(Title), 'SWI-Prolog -- ~w', [Obj]).
  705
  706%!  pldoc_object(+Request)
  707%
  708%   Handler for /doc_for?object=Term, Provide  documentation for the
  709%   given term.
  710
  711pldoc_object(Request) :-
  712    http_parameters(Request,
  713                    [ object(Atom, []),
  714                      header(Header, [default(true)])
  715                    ]),
  716    atom_to_term(Atom, Obj, _),
  717    (   prolog:doc_object_title(Obj, Title)
  718    ->  true
  719    ;   Title = Atom
  720    ),
  721    edit_options(Request, EditOptions),
  722    reply_html_page(
  723        pldoc(object(Obj)),
  724        title(Title),
  725        \object_page(Obj, [header(Header)|EditOptions])).
  726
  727
  728%!  pldoc_search(+Request)
  729%
  730%   Search the collected PlDoc comments and Prolog manual.
  731
  732pldoc_search(Request) :-
  733    http_parameters(Request,
  734                    [ for(For,
  735                          [ optional(true),
  736                            description('String to search for')
  737                          ]),
  738                      page(Page,
  739                           [ integer,
  740                             default(1),
  741                             description('Page of search results to view')
  742                           ]),
  743                      in(In,
  744                         [ oneof([all,app,noapp,man,lib,pack,wiki]),
  745                           default(all),
  746                           description('Search everying, application only or manual only')
  747                         ]),
  748                      match(Match,
  749                            [ oneof([name,summary]),
  750                              default(summary),
  751                              description('Match only the name or also the summary')
  752                            ]),
  753                      resultFormat(Format,
  754                                   [ oneof(long,summary),
  755                                     default(summary),
  756                                     description('Return full documentation or summary-lines')
  757                                   ])
  758                    ]),
  759    edit_options(Request, EditOptions),
  760    format(string(Title), 'Prolog search -- ~w', [For]),
  761    reply_html_page(pldoc(search(For)),
  762                    title(Title),
  763                    \search_reply(For,
  764                                  [ resultFormat(Format),
  765                                    search_in(In),
  766                                    search_match(Match),
  767                                    page(Page)
  768                                  | EditOptions
  769                                  ])).
  770
  771
  772                 /*******************************
  773                 *     HTTP PARAMETER TYPES     *
  774                 *******************************/
  775
  776:- public
  777    param/2.                        % used in pack documentation server
  778
  779param(public_only,
  780      [ boolean,
  781        default(true),
  782        description('If true, hide private predicates')
  783      ]).
  784param(reload,
  785      [ boolean,
  786        default(false),
  787        description('Reload the file and its documentation')
  788      ]).
  789param(show,
  790      [ oneof([doc,src,raw]),
  791        default(doc),
  792        description('How to show the file')
  793      ]).
  794param(format_comments,
  795      [ boolean,
  796        default(true),
  797        description('If true, use PlDoc for rendering structured comments')
  798      ])