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)).
79:- dynamic 80 doc_server_port/1, 81 doc_enabled/0. 82 83httplocation(pldoc, root(pldoc), []). 84httplocation(pldoc_man, pldoc(refman), []). 85httplocation(pldoc_pkg, pldoc(package), []). 86httplocation(pldoc_resource, Path, []) :- 87 http_location_by_id(pldoc_resource, Path).
95doc_enable(true) :- 96 ( doc_enabled 97 -> true 98 ; assertz(doc_enabled) 99 ). 100doc_enable(false) :- 101 retractall(doc_enabled).
ip(A,B,C,D)
. See tcp_host_to_address/2 for
details.allow(HostOrIP)
.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) ]).
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)).
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 ).
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).
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]).
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).
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 ]).
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, []).
localhost
. The call can
edit files using the file
attribute or a predicate if both
name
and arity
is given and optionally module
.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))).
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 ).
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).
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).
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').
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.
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).
edit(true)
in Options if the connection is from the
localhost.602edit_options(Request, [edit(true)]) :- 603 catch(http:authenticate(pldoc(edit), Request, _), _, fail), 604 !. 605edit_options(_, []).
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).
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).
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).
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]).
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])).
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 ])
Documentation server
The module
library(pldoc/http)
provides an embedded HTTP documentation server that allows for browsing the documentation of all files loaded afterlibrary(pldoc)
has been loaded. */