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