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:- 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)).   74
   75/** <module> Documentation server
   76
   77The module library(pldoc/http) provides an   embedded HTTP documentation
   78server that allows for browsing the   documentation  of all files loaded
   79_after_ library(pldoc) has been loaded.
   80*/
   81
   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).
   91
   92%!  doc_enable(+Boolean)
   93%
   94%   Actually activate the PlDoc server. Merely   loading the server does
   95%   not do so to avoid incidental loading   in a user HTTP server making
   96%   the documentation available.
   97
   98doc_enable(true) :-
   99    (   doc_enabled
  100    ->  true
  101    ;   assertz(doc_enabled)
  102    ).
  103doc_enable(false) :-
  104    retractall(doc_enabled).
  105
  106%!  doc_server(?Port) is det.
  107%!  doc_server(?Port, +Options) is det.
  108%
  109%   Start a documentation server in the  current Prolog process. The
  110%   server is started in a separate   thread.  Options are handed to
  111%   http_server/2.  In  addition,   the    following   options   are
  112%   recognised:
  113%
  114%           * allow(HostOrIP)
  115%           Allow connections from HostOrIP.  If HostOrIP is an atom
  116%           it is matched to the hostname.  It if starts with a .,
  117%           suffix match is done, matching the domain.  Finally it
  118%           can be a term ip(A,B,C,D). See tcp_host_to_address/2 for
  119%           details.
  120%
  121%           * deny(HostOrIP)
  122%           See allow(HostOrIP).
  123%
  124%           * edit(Bool)
  125%           Allow editing from localhost connections? Default:
  126%           =true=.
  127%
  128%   The predicate doc_server/1 is defined as below, which provides a
  129%   good default for development.
  130%
  131%   ==
  132%   doc_server(Port) :-
  133%           doc_server(Port,
  134%                      [ allow(localhost)
  135%                      ]).
  136%   ==
  137%
  138%   @see    doc_browser/1
  139
  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.  163
  164%!  doc_current_server(-Port) is det.
  165%
  166%   TCP/IP port of the documentation server.   Fails if no server is
  167%   running. Note that in the current   infrastructure we can easily
  168%   be embedded into another  Prolog  HTTP   server.  If  we are not
  169%   started from doc_server/2, we  return  the   port  of  a running
  170%   HTTP server.
  171%
  172%   @tbd    Trap destruction of the server.
  173%   @error  existence_error(http_server, pldoc)
  174
  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.  186
  187%!  doc_browser is det.
  188%!  doc_browser(+What) is semidet.
  189%
  190%   Open user's default browser on the documentation server.
  191
  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).
  233
  234%!  prepare_editor
  235%
  236%   Start XPCE as edit requests coming from the document server can only
  237%   be handled if XPCE is running.   This must load PceEmacs dynamically
  238%   without triggering the undefined predicate  detection. We cannot use
  239%   conditional compilation as xpce could be  installed later and may or
  240%   may not be available depending on  the ``--no-pce`` and availability
  241%   of a GUI.
  242
  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]).  275
  276
  277%!  pldoc_root(+Request)
  278%
  279%   Reply using the index-page  of   the  Prolog  working directory.
  280%   There are various options for the   start directory. For example
  281%   we could also use the file or   directory of the file that would
  282%   be edited using edit/0.
  283
  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).
  308
  309
  310%!  pldoc_index(+Request)
  311%
  312%   HTTP handle for /index.html, providing an overall overview
  313%   of the available documentation.
  314
  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                    ]).
  322
  323
  324%!  pldoc_file(+Request)
  325%
  326%   Hander for /file?file=File, providing documentation for File.
  327
  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, []).
  337
  338%!  pldoc_edit(+Request)
  339%
  340%   HTTP handler that starts the user's   default editor on the host
  341%   running the server. This  handler  can   only  accessed  if  the
  342%   browser connection originates from  =localhost=.   The  call can
  343%   edit files using the =file=  attribute   or  a predicate if both
  344%   =name= and =arity= is given and optionally =module=.
  345
  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))).
  392
  393
  394%!  go_place(+Request)
  395%
  396%   HTTP handler to handle the places menu.
  397
  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    ).
  416
  417
  418%!  allowed_directory(+Dir) is semidet.
  419%
  420%   True if we are allowed to produce and index for Dir.
  421
  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).
  430
  431
  432%!  allowed_file(+File) is semidet.
  433%
  434%   True if we are allowed to serve   File.  Currently means we have
  435%   predicates loaded from File or the directory must be allowed.
  436
  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).
  444
  445
  446%!  pldoc_resource(+Request)
  447%
  448%   Handler for /res/File, serving CSS, JS and image files.
  449
  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').
  473
  474
  475%!  pldoc_doc(+Request)
  476%
  477%   Handler for /doc/Path
  478%
  479%   Reply documentation of a file. Path is  the absolute path of the
  480%   file for which to return the  documentation. Extension is either
  481%   none, the Prolog extension or the HTML extension.
  482%
  483%   Note that we reply  with  pldoc.css   if  the  file  basename is
  484%   pldoc.css to allow for a relative link from any directory.
  485
  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).
  614
  615
  616%!  edit_options(+Request, -Options) is det.
  617%
  618%   Return edit(true) in Options  if  the   connection  is  from the
  619%   localhost.
  620
  621edit_options(Request, [edit(true)]) :-
  622    catch(http:authenticate(pldoc(edit), Request, _), _, fail),
  623    !.
  624edit_options(_, []).
  625
  626
  627%!  pl_file(+File, -PlFile) is semidet.
  628
  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).
  639
  640%!  wiki_file(+File, -TxtFile) is semidet.
  641%
  642%   True if TxtFile is an existing file  that must be served as wiki
  643%   file.
  644
  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).
  663
  664
  665%!  clean_path(+AfterDoc, -AbsPath)
  666%
  667%   Restore the path, Notably deals Windows issues
  668
  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).
  675
  676
  677%!  pldoc_man(+Request)
  678%
  679%   Handler for /man, offering one of the parameters:
  680%
  681%       * predicate=PI
  682%       providing documentation from the manual on the predicate PI.
  683%       * function=PI
  684%       providing documentation from the manual on the function PI.
  685%       * 'CAPI'=F
  686%       providing documentation from the manual on the C-function F.
  687
  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]).
  731
  732%!  pldoc_object(+Request)
  733%
  734%   Handler for /doc_for?object=Term, Provide  documentation for the
  735%   given term.
  736
  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])).
  755
  756
  757%!  pldoc_search(+Request)
  758%
  759%   Search the collected PlDoc comments and Prolog manual.
  760
  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      ])