35
36:- module(pldoc_http,
37 [ doc_server/1, 38 doc_server/2, 39 doc_browser/0,
40 doc_browser/1 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
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
122
123doc_server(Port) :-
124 doc_server(Port,
125 [ allow(localhost),
126 allow(ip(127,0,0,1)) 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
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
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
212
213prepare_editor :-
214 current_prolog_flag(editor, pce_emacs),
215 !,
216 start_emacs.
217prepare_editor.
218
219
220 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
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
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
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
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
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
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
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
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
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), 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), 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), 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. 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
581
582edit_options(Request, [edit(true)]) :-
583 catch(http:authenticate(pldoc(edit), Request, _), _, fail),
584 !.
585edit_options(_, []).
586
587
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
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
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
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
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
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 749
750:- public
751 param/2. 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 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 ].