35
36:- module(pldoc_http,
37 [ doc_enable/1, 38 doc_server/1, 39 doc_server/2, 40 doc_browser/0,
41 doc_browser/1 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
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
97
98doc_enable(true) :-
99 ( doc_enabled
100 -> true
101 ; assertz(doc_enabled)
102 ).
103doc_enable(false) :-
104 retractall(doc_enabled).
105
139
140doc_server(Port) :-
141 doc_server(Port,
142 [ allow(localhost),
143 allow(ip(127,0,0,1)) 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
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
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
238
239prepare_editor :-
240 current_prolog_flag(editor, pce_emacs),
241 !,
242 start_emacs.
243prepare_editor.
244
245
246 249
250:- http_handler(pldoc(.), pldoc_root,
251 [ prefix,
252 authentication(pldoc(read)),
253 condition(doc_enabled)
254 ]). 255:- http_handler(pldoc('index.html'), pldoc_index, []). 256:- http_handler(pldoc(file), pldoc_file, []). 257:- http_handler(pldoc(place), go_place, []). 258:- http_handler(pldoc(edit), pldoc_edit,
259 [authentication(pldoc(edit))]). 260:- http_handler(pldoc(doc), pldoc_doc, [prefix]). 261:- http_handler(pldoc(man), pldoc_man, []). 262:- http_handler(pldoc(doc_for), pldoc_object, [id(pldoc_doc_for)]). 263:- http_handler(pldoc(search), pldoc_search, []). 264:- http_handler(pldoc('res/'), pldoc_resource, [prefix]). 265
266
273
274pldoc_root(Request) :-
275 http_parameters(Request,
276 [ empty(Empty, [ oneof([true,false]),
277 default(false)
278 ])
279 ]),
280 pldoc_root(Request, Empty).
281
282pldoc_root(Request, false) :-
283 http_location_by_id(pldoc_root, Root),
284 memberchk(path(Path), Request),
285 Root \== Path,
286 !,
287 existence_error(http_location, Path).
288pldoc_root(_Request, false) :-
289 working_directory(Dir0, Dir0),
290 allowed_directory(Dir0),
291 !,
292 ensure_slash_end(Dir0, Dir1),
293 doc_file_href(Dir1, Ref0),
294 atom_concat(Ref0, 'index.html', Index),
295 throw(http_reply(see_other(Index))).
296pldoc_root(Request, _) :-
297 pldoc_index(Request).
298
299
304
305pldoc_index(_Request) :-
306 reply_html_page(pldoc(index),
307 title('SWI-Prolog documentation'),
308 [ \doc_links('', []),
309 h1('SWI-Prolog documentation'),
310 \man_overview([])
311 ]).
312
313
317
318pldoc_file(Request) :-
319 http_parameters(Request,
320 [ file(File, [])
321 ]),
322 ( source_file(File)
323 -> true
324 ; throw(http_reply(forbidden(File)))
325 ),
326 doc_for_file(File, []).
327
335
336pldoc_edit(Request) :-
337 http:authenticate(pldoc(edit), Request, _),
338 http_parameters(Request,
339 [ file(File,
340 [ optional(true),
341 description('Name of the file to edit')
342 ]),
343 line(Line,
344 [ optional(true),
345 integer,
346 description('Line in the file')
347 ]),
348 name(Name,
349 [ optional(true),
350 description('Name of a Prolog predicate to edit')
351 ]),
352 arity(Arity,
353 [ integer,
354 optional(true),
355 description('Arity of a Prolog predicate to edit')
356 ]),
357 module(Module,
358 [ optional(true),
359 description('Name of a Prolog module to search for predicate')
360 ])
361 ]),
362 ( atom(File)
363 -> allowed_file(File)
364 ; true
365 ),
366 ( atom(File), integer(Line)
367 -> Edit = file(File, line(Line))
368 ; atom(File)
369 -> Edit = file(File)
370 ; atom(Name), integer(Arity)
371 -> ( atom(Module)
372 -> Edit = (Module:Name/Arity)
373 ; Edit = (Name/Arity)
374 )
375 ),
376 edit(Edit),
377 format('Content-type: text/plain~n~n'),
378 format('Started ~q~n', [edit(Edit)]).
379pldoc_edit(_Request) :-
380 http_location_by_id(pldoc_edit, Location),
381 throw(http_reply(forbidden(Location))).
382
383
387
388go_place(Request) :-
389 http_parameters(Request,
390 [ place(Place, [])
391 ]),
392 places(Place).
393
394places(':packs:') :-
395 !,
396 http_link_to_id(pldoc_pack, [], HREF),
397 throw(http_reply(moved(HREF))).
398places(Dir0) :-
399 expand_alias(Dir0, Dir),
400 ( allowed_directory(Dir)
401 -> format(string(IndexFile), '~w/index.html', [Dir]),
402 doc_file_href(IndexFile, HREF),
403 throw(http_reply(moved(HREF)))
404 ; throw(http_reply(forbidden(Dir)))
405 ).
406
407
411
412allowed_directory(Dir) :-
413 source_directory(Dir),
414 !.
415allowed_directory(Dir) :-
416 working_directory(CWD, CWD),
417 same_file(CWD, Dir).
418allowed_directory(Dir) :-
419 prolog:doc_directory(Dir).
420
421
426
427allowed_file(File) :-
428 source_file(_, File),
429 !.
430allowed_file(File) :-
431 absolute_file_name(File, Canonical),
432 file_directory_name(Canonical, Dir),
433 allowed_directory(Dir).
434
435
439
440pldoc_resource(Request) :-
441 http_location_by_id(pldoc_resource, ResRoot),
442 memberchk(path(Path), Request),
443 atom_concat(ResRoot, File, Path),
444 file(File, Local),
445 http_reply_file(pldoc(Local), [], Request).
446
447file('pldoc.css', 'pldoc.css').
448file('pllisting.css', 'pllisting.css').
449file('pldoc.js', 'pldoc.js').
450file('edit.png', 'edit.png').
451file('editpred.png', 'editpred.png').
452file('up.gif', 'up.gif').
453file('source.png', 'source.png').
454file('public.png', 'public.png').
455file('private.png', 'private.png').
456file('reload.png', 'reload.png').
457file('favicon.ico', 'favicon.ico').
458file('h1-bg.png', 'h1-bg.png').
459file('h2-bg.png', 'h2-bg.png').
460file('pub-bg.png', 'pub-bg.png').
461file('priv-bg.png', 'priv-bg.png').
462file('multi-bg.png', 'multi-bg.png').
463
464
475
476pldoc_doc(Request) :-
477 memberchk(path(ReqPath), Request),
478 http_location_by_id(pldoc_doc, Me),
479 atom_concat(Me, AbsFile0, ReqPath),
480 ( sub_atom(ReqPath, _, _, 0, /)
481 -> atom_concat(ReqPath, 'index.html', File),
482 throw(http_reply(moved(File)))
483 ; clean_path(AbsFile0, AbsFile1),
484 expand_alias(AbsFile1, AbsFile),
485 is_absolute_file_name(AbsFile)
486 -> documentation(AbsFile, Request)
487 ).
488
489documentation(Path, Request) :-
490 file_base_name(Path, Base),
491 file(_, Base), 492 !,
493 http_reply_file(pldoc(Base), [], Request).
494documentation(Path, Request) :-
495 file_name_extension(_, Ext, Path),
496 autolink_extension(Ext, image),
497 http_reply_file(Path, [unsafe(true)], Request).
498documentation(Path, Request) :-
499 Index = '/index.html',
500 sub_atom(Path, _, _, 0, Index),
501 atom_concat(Dir, Index, Path),
502 exists_directory(Dir), 503 !,
504 ( allowed_directory(Dir)
505 -> edit_options(Request, EditOptions),
506 doc_for_dir(Dir, EditOptions)
507 ; throw(http_reply(forbidden(Dir)))
508 ).
509documentation(File, Request) :-
510 wiki_file(File, WikiFile),
511 !,
512 ( allowed_file(WikiFile)
513 -> true
514 ; throw(http_reply(forbidden(File)))
515 ),
516 edit_options(Request, Options),
517 doc_for_wiki_file(WikiFile, Options).
518documentation(Path, Request) :-
519 pl_file(Path, File),
520 !,
521 ( allowed_file(File)
522 -> true
523 ; throw(http_reply(forbidden(File)))
524 ),
525 doc_reply_file(File, Request).
526documentation(Path, _) :-
527 throw(http_reply(not_found(Path))).
528
529:- public
530 doc_reply_file/2. 531
532doc_reply_file(File, Request) :-
533 http_parameters(Request,
534 [ public_only(Public),
535 reload(Reload),
536 show(Show),
537 format_comments(FormatComments)
538 ],
539 [ attribute_declarations(param)
540 ]),
541 ( exists_file(File)
542 -> true
543 ; throw(http_reply(not_found(File)))
544 ),
545 ( Reload == true,
546 source_file(File)
547 -> load_files(File, [if(changed), imports([])])
548 ; true
549 ),
550 edit_options(Request, EditOptions),
551 ( Show == src
552 -> format('Content-type: text/html~n~n', []),
553 source_to_html(File, stream(current_output),
554 [ skin(src_skin(Request, Show, FormatComments)),
555 format_comments(FormatComments)
556 ])
557 ; Show == raw
558 -> http_reply_file(File,
559 [ unsafe(true), 560 mime_type(text/plain)
561 ], Request)
562 ; doc_for_file(File,
563 [ public_only(Public),
564 source_link(true)
565 | EditOptions
566 ])
567 ).
568
569
570:- public src_skin/5. 571
572src_skin(Request, _Show, FormatComments, header, Out) :-
573 memberchk(request_uri(ReqURI), Request),
574 negate(FormatComments, AltFormatComments),
575 replace_parameters(ReqURI, [show(raw)], RawLink),
576 replace_parameters(ReqURI, [format_comments(AltFormatComments)], CmtLink),
577 phrase(html(div(class(src_formats),
578 [ 'View source with ',
579 a(href(CmtLink), \alt_view(AltFormatComments)),
580 ' or as ',
581 a(href(RawLink), raw)
582 ])), Tokens),
583 print_html(Out, Tokens).
584
585alt_view(true) -->
586 html('formatted comments').
587alt_view(false) -->
588 html('raw comments').
589
590negate(true, false).
591negate(false, true).
592
593replace_parameters(ReqURI, Extra, URI) :-
594 uri_components(ReqURI, C0),
595 uri_data(search, C0, Search0),
596 ( var(Search0)
597 -> uri_query_components(Search, Extra)
598 ; uri_query_components(Search0, Form0),
599 merge_options(Extra, Form0, Form),
600 uri_query_components(Search, Form)
601 ),
602 uri_data(search, C0, Search, C),
603 uri_components(URI, C).
604
605
610
611edit_options(Request, [edit(true)]) :-
612 catch(http:authenticate(pldoc(edit), Request, _), _, fail),
613 !.
614edit_options(_, []).
615
616
618
619pl_file(File, PlFile) :-
620 file_name_extension(Base, html, File),
621 !,
622 absolute_file_name(Base,
623 PlFile,
624 [ file_errors(fail),
625 file_type(prolog),
626 access(read)
627 ]).
628pl_file(File, File).
629
634
635wiki_file(File, TxtFile) :-
636 file_name_extension(_, Ext, File),
637 wiki_file_extension(Ext),
638 !,
639 TxtFile = File.
640wiki_file(File, TxtFile) :-
641 file_base_name(File, Base),
642 autolink_file(Base, wiki),
643 !,
644 TxtFile = File.
645wiki_file(File, TxtFile) :-
646 file_name_extension(Base, html, File),
647 wiki_file_extension(Ext),
648 file_name_extension(Base, Ext, TxtFile),
649 access_file(TxtFile, read).
650
651wiki_file_extension(md).
652wiki_file_extension(txt).
653
654
658
659clean_path(Path0, Path) :-
660 current_prolog_flag(windows, true),
661 sub_atom(Path0, 2, _, _, :),
662 !,
663 sub_atom(Path0, 1, _, 0, Path).
664clean_path(Path, Path).
665
666
677
678pldoc_man(Request) :-
679 http_parameters(Request,
680 [ predicate(PI, [optional(true)]),
681 function(Fun, [optional(true)]),
682 'CAPI'(F, [optional(true)]),
683 section(Sec, [optional(true)])
684 ]),
685 ( ground(PI)
686 -> atom_pi(PI, Obj)
687 ; ground(Fun)
688 -> atomic_list_concat([Name,ArityAtom], /, Fun),
689 atom_number(ArityAtom, Arity),
690 Obj = f(Name/Arity)
691 ; ground(F)
692 -> Obj = c(F)
693 ; ground(Sec)
694 -> atom_concat('sec:', Sec, SecID),
695 Obj = section(SecID)
696 ),
697 man_title(Obj, Title),
698 reply_html_page(
699 pldoc(object(Obj)),
700 title(Title),
701 \man_page(Obj, [])).
702
703man_title(f(Obj), Title) :-
704 !,
705 format(atom(Title), 'SWI-Prolog -- function ~w', [Obj]).
706man_title(c(Obj), Title) :-
707 !,
708 format(atom(Title), 'SWI-Prolog -- API-function ~w', [Obj]).
709man_title(section(_Id), Title) :-
710 !,
711 format(atom(Title), 'SWI-Prolog -- Manual', []).
712man_title(Obj, Title) :-
713 format(atom(Title), 'SWI-Prolog -- ~w', [Obj]).
714
719
720pldoc_object(Request) :-
721 http_parameters(Request,
722 [ object(Atom, []),
723 header(Header, [default(true)])
724 ]),
725 ( catch(atom_to_term(Atom, Obj, _), error(_,_), fail)
726 -> true
727 ; atom_to_object(Atom, Obj)
728 ),
729 ( prolog:doc_object_title(Obj, Title)
730 -> true
731 ; Title = Atom
732 ),
733 edit_options(Request, EditOptions),
734 reply_html_page(
735 pldoc(object(Obj)),
736 title(Title),
737 \object_page(Obj, [header(Header)|EditOptions])).
738
739
743
744pldoc_search(Request) :-
745 http_parameters(Request,
746 [ for(For,
747 [ optional(true),
748 description('String to search for')
749 ]),
750 page(Page,
751 [ integer,
752 default(1),
753 description('Page of search results to view')
754 ]),
755 in(In,
756 [ oneof([all,app,noapp,man,lib,pack,wiki]),
757 default(all),
758 description('Search everying, application only or manual only')
759 ]),
760 match(Match,
761 [ oneof([name,summary]),
762 default(summary),
763 description('Match only the name or also the summary')
764 ]),
765 resultFormat(Format,
766 [ oneof(long,summary),
767 default(summary),
768 description('Return full documentation or summary-lines')
769 ])
770 ]),
771 edit_options(Request, EditOptions),
772 format(string(Title), 'Prolog search -- ~w', [For]),
773 reply_html_page(pldoc(search(For)),
774 title(Title),
775 \search_reply(For,
776 [ resultFormat(Format),
777 search_in(In),
778 search_match(Match),
779 page(Page)
780 | EditOptions
781 ])).
782
783
784 787
788:- public
789 param/2. 790
791param(public_only,
792 [ boolean,
793 default(true),
794 description('If true, hide private predicates')
795 ]).
796param(reload,
797 [ boolean,
798 default(false),
799 description('Reload the file and its documentation')
800 ]).
801param(show,
802 [ oneof([doc,src,raw]),
803 default(doc),
804 description('How to show the file')
805 ]).
806param(format_comments,
807 [ boolean,
808 default(true),
809 description('If true, use PlDoc for rendering structured comments')
810 ])