36
37:- module(prolog_main,
38 [ main/0,
39 argv_options/3, 40 argv_options/4, 41 argv_usage/1, 42 cli_parse_debug_options/2, 43 cli_enable_development_system/0
44 ]). 46:- autoload(library(debug)). 47:- autoload(library(threadutil)). 49:- autoload(library(apply), [maplist/3, partition/4]). 50:- autoload(library(lists), [append/3]). 51:- autoload(library(pairs), [pairs_keys/2, pairs_values/2]). 52:- autoload(library(prolog_code), [pi_head/2]). 53:- autoload(library(prolog_debug), [spy/1]). 54:- autoload(library(dcg/high_order), [sequence//3, sequence//2]). 55:- autoload(library(option), [option/2]). 56
57:- meta_predicate
58 argv_options(:, -, -),
59 argv_options(:, -, -, +),
60 argv_usage(:). 61
62:- dynamic
63 interactive/0. 64
93
94:- module_transparent
95 main/0. 96
102
103main :-
104 context_module(M),
105 set_signals,
106 current_prolog_flag(argv, Av),
107 catch_with_backtrace(M:main(Av), Error, throw(Error)),
108 ( interactive
109 -> cli_enable_development_system
110 ; true
111 ).
112
113set_signals :-
114 on_signal(int, _, interrupt).
115
120
121interrupt(_Sig) :-
122 halt(1).
123
124 127
211
212argv_options(M:Argv, Positional, Options) :-
213 in(M:opt_type(_,_,_)),
214 !,
215 argv_options(M:Argv, Positional, Options, [on_error(halt(1))]).
216argv_options(_:Argv, Positional, Options) :-
217 argv_untyped_options(Argv, Positional, Options).
218
233
234argv_options(Argv, Positional, Options, POptions) :-
235 option(on_error(halt(Code)), POptions),
236 !,
237 E = error(_,_),
238 catch(opt_parse(Argv, Positional, Options, POptions), E,
239 ( print_message(error, E),
240 halt(Code)
241 )).
242argv_options(Argv, Positional, Options, POptions) :-
243 opt_parse(Argv, Positional, Options, POptions).
244
252
253argv_untyped_options([], Pos, Opts) =>
254 Pos = [], Opts = [].
255argv_untyped_options([--|R], Pos, Ops) =>
256 Pos = R, Ops = [].
257argv_untyped_options([H0|T0], R, Ops), sub_atom(H0, 0, _, _, --) =>
258 Ops = [H|T],
259 ( sub_atom(H0, B, _, A, =)
260 -> B2 is B-2,
261 sub_atom(H0, 2, B2, _, Name),
262 sub_string(H0, _, A, 0, Value0),
263 convert_option(Name, Value0, Value)
264 ; sub_atom(H0, 2, _, 0, Name0),
265 ( sub_atom(Name0, 0, _, _, 'no-')
266 -> sub_atom(Name0, 3, _, 0, Name),
267 Value = false
268 ; Name = Name0,
269 Value = true
270 )
271 ),
272 canonical_name(Name, PlName),
273 H =.. [PlName,Value],
274 argv_untyped_options(T0, R, T).
275argv_untyped_options([H|T0], Ops, T) =>
276 Ops = [H|R],
277 argv_untyped_options(T0, R, T).
278
279convert_option(password, String, String) :- !.
280convert_option(_, String, Number) :-
281 number_string(Number, String),
282 !.
283convert_option(_, String, Atom) :-
284 atom_string(Atom, String).
285
286canonical_name(Name, PlName) :-
287 split_string(Name, "-_", "", Parts),
288 atomic_list_concat(Parts, '_', PlName).
289
299
300opt_parse(M:Argv, _Positional, _Options, _POptions) :-
301 opt_needs_help(M:Argv),
302 !,
303 argv_usage(M:debug),
304 halt(0).
305opt_parse(M:Argv, Positional, Options, POptions) :-
306 opt_parse(Argv, Positional, Options, M, POptions).
307
308opt_needs_help(M:[Arg]) :-
309 in(M:opt_type(_, help, boolean)),
310 !,
311 in(M:opt_type(Opt, help, boolean)),
312 ( short_opt(Opt)
313 -> atom_concat(-, Opt, Arg)
314 ; atom_concat(--, Opt, Arg)
315 ),
316 !.
317opt_needs_help(_:['-h']).
318opt_needs_help(_:['-?']).
319opt_needs_help(_:['--help']).
320
321opt_parse([], Positional, Options, _, _) =>
322 Positional = [],
323 Options = [].
324opt_parse([--|T], Positional, Options, _, _) =>
325 Positional = T,
326 Options = [].
327opt_parse([H|T], Positional, Options, M, POptions), atom_concat(--, Long, H) =>
328 take_long(Long, T, Positional, Options, M, POptions).
329opt_parse([H|T], Positional, Options, M, POptions),
330 H \== '-',
331 string_concat(-, Opts, H) =>
332 string_chars(Opts, Shorts),
333 take_shorts(Shorts, T, Positional, Options, M, POptions).
334opt_parse(Argv, Positional, Options, _M, POptions),
335 option(options_after_arguments(false), POptions) =>
336 Positional = Argv,
337 Options = [].
338opt_parse([H|T], Positional, Options, M, POptions) =>
339 Positional = [H|PT],
340 opt_parse(T, PT, Options, M, POptions).
341
342
343take_long(Long, T, Positional, Options, M, POptions) :- 344 sub_atom(Long, B, _, A, =),
345 !,
346 sub_atom(Long, 0, B, _, LName0),
347 sub_atom(Long, _, A, 0, VAtom),
348 canonical_name(LName0, LName),
349 ( in(M:opt_type(LName, Name, Type))
350 -> opt_value(Type, Long, VAtom, Value),
351 Opt =.. [Name,Value],
352 Options = [Opt|OptionsT],
353 opt_parse(T, Positional, OptionsT, M, POptions)
354 ; opt_error(unknown_option(M:LName0))
355 ).
356take_long(LName0, T, Positional, Options, M, POptions) :- 357 canonical_name(LName0, LName),
358 take_long_(LName, T, Positional, Options, M, POptions).
359
360take_long_(Long, T, Positional, Options, M, POptions) :- 361 opt_bool_type(Long, Name, Value, M),
362 !,
363 Opt =.. [Name,Value],
364 Options = [Opt|OptionsT],
365 opt_parse(T, Positional, OptionsT, M, POptions).
366take_long_(Long, T, Positional, Options, M, POptions) :- 367 ( atom_concat('no_', LName, Long)
368 ; atom_concat('no', LName, Long)
369 ),
370 opt_bool_type(LName, Name, Value0, M),
371 !,
372 negate(Value0, Value),
373 Opt =.. [Name,Value],
374 Options = [Opt|OptionsT],
375 opt_parse(T, Positional, OptionsT, M, POptions).
376take_long_(Long, T, Positional, Options, M, POptions) :- 377 in(M:opt_type(Long, Name, Type)),
378 !,
379 ( T = [VAtom|T1]
380 -> opt_value(Type, Long, VAtom, Value),
381 Opt =.. [Name,Value],
382 Options = [Opt|OptionsT],
383 opt_parse(T1, Positional, OptionsT, M, POptions)
384 ; opt_error(missing_value(Long, Type))
385 ).
386take_long_(Long, _, _, _, M, _) :-
387 opt_error(unknown_option(M:Long)).
388
389take_shorts([], T, Positional, Options, M, POptions) :-
390 opt_parse(T, Positional, Options, M, POptions).
391take_shorts([H|T], Argv, Positional, Options, M, POptions) :-
392 opt_bool_type(H, Name, Value, M),
393 !,
394 Opt =.. [Name,Value],
395 Options = [Opt|OptionsT],
396 take_shorts(T, Argv, Positional, OptionsT, M, POptions).
397take_shorts([H|T], Argv, Positional, Options, M, POptions) :-
398 in(M:opt_type(H, Name, Type)),
399 !,
400 ( T == []
401 -> ( Argv = [VAtom|ArgvT]
402 -> opt_value(Type, H, VAtom, Value),
403 Opt =.. [Name,Value],
404 Options = [Opt|OptionsT],
405 take_shorts(T, ArgvT, Positional, OptionsT, M, POptions)
406 ; opt_error(missing_value(H, Type))
407 )
408 ; atom_chars(VAtom, T),
409 opt_value(Type, H, VAtom, Value),
410 Opt =.. [Name,Value],
411 Options = [Opt|OptionsT],
412 take_shorts([], Argv, Positional, OptionsT, M, POptions)
413 ).
414take_shorts([H|_], _, _, _, M, _) :-
415 opt_error(unknown_option(M:H)).
416
417opt_bool_type(Opt, Name, Value, M) :-
418 in(M:opt_type(Opt, Name, Type)),
419 ( Type == boolean
420 -> Value = true
421 ; Type = boolean(Value)
422 ).
423
424negate(true, false).
425negate(false, true).
426
430
431opt_value(Type, _Opt, VAtom, Value) :-
432 opt_convert(Type, VAtom, Value),
433 !.
434opt_value(Type, Opt, VAtom, _) :-
435 opt_error(value_type(Opt, Type, VAtom)).
436
438
439opt_convert(A|B, Spec, Value) :-
440 ( opt_convert(A, Spec, Value)
441 -> true
442 ; opt_convert(B, Spec, Value)
443 ).
444opt_convert(boolean, Spec, Value) :-
445 to_bool(Spec, Value).
446opt_convert(boolean(_), Spec, Value) :-
447 to_bool(Spec, Value).
448opt_convert(integer, Spec, Value) :-
449 atom_number(Spec, Value),
450 integer(Value).
451opt_convert(float, Spec, Value) :-
452 atom_number(Spec, Value0),
453 Value is float(Value0).
454opt_convert(nonneg, Spec, Value) :-
455 atom_number(Spec, Value),
456 integer(Value),
457 Value >= 0.
458opt_convert(natural, Spec, Value) :-
459 atom_number(Spec, Value),
460 integer(Value),
461 Value >= 1.
462opt_convert(between(Low, High), Spec, Value) :-
463 atom_number(Spec, Value0),
464 ( ( float(Low) ; float(High) )
465 -> Value is float(Value0)
466 ; integer(Value0),
467 Value = Value0
468 ),
469 Value >= Low, Value =< High.
470opt_convert(atom, Value, Value).
471opt_convert(oneof(List), Value, Value) :-
472 memberchk(Value, List).
473opt_convert(string, Value0, Value) :-
474 atom_string(Value0, Value).
475opt_convert(file, Spec, Value) :-
476 prolog_to_os_filename(Value, Spec).
477opt_convert(file(Access), Spec, Value) :-
478 ( Spec == '-'
479 -> Value = '-'
480 ; prolog_to_os_filename(Value, Spec),
481 ( access_file(Value, Access)
482 -> true
483 ; opt_error(access_file(Spec, Access))
484 )
485 ).
486opt_convert(term, Spec, Value) :-
487 term_string(Value, Spec, []).
488opt_convert(term(Options), Spec, Value) :-
489 term_string(Term, Spec, Options),
490 ( option(variable_names(Bindings), Options)
491 -> Value = Term-Bindings
492 ; Value = Term
493 ).
494
495to_bool(true, true).
496to_bool('True', true).
497to_bool('TRUE', true).
498to_bool(on, true).
499to_bool('On', true).
500to_bool('1', true).
501to_bool(false, false).
502to_bool('False', false).
503to_bool('FALSE', false).
504to_bool(off, false).
505to_bool('Off', false).
506to_bool('0', false).
507
534
535argv_usage(M:Level) :-
536 print_message(Level, opt_usage(M)).
537
538:- multifile
539 prolog:message//1. 540
541prolog:message(opt_usage(M)) -->
542 usage(M).
543
544usage(M) -->
545 usage_text(M:header),
546 usage_line(M),
547 usage_options(M),
548 usage_text(M:footer).
549
554
555usage_text(M:Which) -->
556 { in(M:opt_help(help(Which), Help))
557 },
558 !,
559 ( {Which == header}
560 -> user_text(M:Help), [nl]
561 ; [nl], user_text(M:Help)
562 ).
563usage_text(_) -->
564 [].
565
566user_text(M:Entries) -->
567 { is_list(Entries) },
568 sequence(help_elem(M), Entries).
569user_text(_:Help) -->
570 [ '~w'-[Help] ].
571
572help_elem(M, \Callable) -->
573 { callable(Callable) },
574 call(M:Callable),
575 !.
576help_elem(_M, Elem) -->
577 [ Elem ].
578
579usage_line(M) -->
580 [ ansi(comment, 'Usage: ', []) ],
581 cmdline(M),
582 ( {in(M:opt_help(help(usage), Help))}
583 -> user_text(M:Help)
584 ; [ ' [options]'-[] ]
585 ),
586 [ nl, nl ].
587
588cmdline(_M) -->
589 { current_prolog_flag(associated_file, AbsFile),
590 file_base_name(AbsFile, Base),
591 current_prolog_flag(os_argv, Argv),
592 append(Pre, [File|_], Argv),
593 file_base_name(File, Base),
594 append(Pre, [File], Cmd),
595 !
596 },
597 sequence(cmdarg, [' '-[]], Cmd).
598cmdline(_M) -->
599 { current_prolog_flag(saved_program, true),
600 current_prolog_flag(os_argv, OsArgv),
601 append(_, ['-x', State|_], OsArgv),
602 !
603 },
604 cmdarg(State).
605cmdline(_M) -->
606 { current_prolog_flag(os_argv, [Argv0|_])
607 },
608 cmdarg(Argv0).
609
610cmdarg(A) -->
611 [ '~w'-[A] ].
612
618
619usage_options(M) -->
620 { findall(Opt, get_option(M, Opt), Opts),
621 maplist(options_width, Opts, OptWidths),
622 max_list(OptWidths, MaxOptWidth),
623 catch(tty_size(_, Width), _, Width = 80),
624 OptColW is min(MaxOptWidth, 30),
625 HelpColW is Width-4-OptColW
626 },
627 [ ansi(comment, 'Options:', []), nl ],
628 sequence(opt_usage(OptColW, HelpColW), [nl], Opts).
629
630opt_usage(OptColW, HelpColW, opt(_Name, Type, Short, Long, Help, Meta)) -->
631 options(Type, Short, Long, Meta),
632 [ '~t~*:| '-[OptColW] ],
633 help_text(Help, OptColW, HelpColW).
634
635help_text([First|Lines], Indent, _Width) -->
636 !,
637 [ '~w'-[First], nl ],
638 sequence(rest_line(Indent), [nl], Lines).
639help_text(Text, _Indent, Width) -->
640 { string_length(Text, Len),
641 Len =< Width
642 },
643 !,
644 [ '~w'-[Text] ].
645help_text(Text, Indent, Width) -->
646 { wrap_text(Width, Text, [First|Lines])
647 },
648 [ '~w'-[First], nl ],
649 sequence(rest_line(Indent), [nl], Lines).
650
651rest_line(Indent, Line) -->
652 [ '~t~*| ~w'-[Indent, Line] ].
653
659
660wrap_text(Width, Text, Wrapped) :-
661 split_string(Text, " \t\n", " \t\n", Words),
662 wrap_lines(Words, Width, Wrapped).
663
664wrap_lines([], _, []).
665wrap_lines([H|T0], Width, [Line|Lines]) :-
666 !,
667 string_length(H, Len),
668 take_line(T0, T1, Width, Len, LineWords),
669 atomics_to_string([H|LineWords], " ", Line),
670 wrap_lines(T1, Width, Lines).
671
672take_line([H|T0], T, Width, Here, [H|Line]) :-
673 string_length(H, Len),
674 NewHere is Here+Len+1,
675 NewHere =< Width,
676 !,
677 take_line(T0, T, Width, NewHere, Line).
678take_line(T, T, _, _, []).
679
683
684options(Type, ShortOpt, LongOpts, Meta) -->
685 { append(ShortOpt, LongOpts, Opts) },
686 sequence(option(Type, Meta), [', '-[]], Opts).
687
688option(boolean, _, Opt) -->
689 opt(Opt).
690option(_, Meta, Opt) -->
691 opt(Opt),
692 ( { short_opt(Opt) }
693 -> [ ' '-[] ]
694 ; [ '='-[] ]
695 ),
696 [ ansi(var, '~w', [Meta]) ].
697
701
702options_width(opt(_Name, boolean, Short, Long, _Help, _Meta), W) =>
703 length(Short, SCount),
704 length(Long, LCount),
705 maplist(atom_length, Long, LLens),
706 sum_list(LLens, LLen),
707 W is ((SCount+LCount)-1)*2 + 708 SCount*2 +
709 LCount*2 + LLen.
710options_width(opt(_Name, _Type, Short, Long, _Help, Meta), W) =>
711 length(Short, SCount),
712 length(Long, LCount),
713 atom_length(Meta, MLen),
714 maplist(atom_length, Long, LLens),
715 sum_list(LLens, LLen),
716 W is ((SCount+LCount)-1)*2 + 717 SCount*3 + SCount*MLen +
718 LCount*3 + LLen + LCount*MLen.
719
725
726get_option(M, opt(help, boolean, [h,?], [help],
727 Help, -)) :-
728 \+ in(M:opt_type(_, help, boolean)), 729 ( in(M:opt_help(help, Help))
730 -> true
731 ; Help = "Show this help message and exit"
732 ).
733get_option(M, opt(Name, Type, Short, Long, Help, Meta)) :-
734 findall(Name, in(M:opt_type(_, Name, _)), Names),
735 list_to_set(Names, UNames),
736 member(Name, UNames),
737 findall(Opt-Type,
738 in(M:opt_type(Opt, Name, Type)),
739 Pairs),
740 option_type(Name, Pairs, TypeT),
741 functor(TypeT, Type, _),
742 pairs_keys(Pairs, Opts),
743 partition(short_opt, Opts, Short, Long),
744 ( in(M:opt_help(Name, Help))
745 -> true
746 ; Help = ''
747 ),
748 ( in(M:opt_meta(Name, Meta))
749 -> true
750 ; upcase_atom(Type, Meta)
751 ).
752
753option_type(Name, Pairs, Type) :-
754 pairs_values(Pairs, Types),
755 sort(Types, [Type|UTypes]),
756 ( UTypes = []
757 -> true
758 ; print_message(warning,
759 error(opt_error(multiple_types(Name, [Type|UTypes])),_))
760 ).
761
766
767in(Goal) :-
768 pi_head(PI, Goal),
769 current_predicate(PI),
770 call(Goal).
771
772short_opt(Opt) :-
773 atom_length(Opt, 1).
774
775 778
782
783opt_error(Error) :-
784 throw(error(opt_error(Error), _)).
785
786:- multifile
787 prolog:error_message//1. 788
789prolog:error_message(opt_error(Error)) -->
790 opt_error(Error).
791
792opt_error(unknown_option(M:Opt)) -->
793 [ 'Unknown option: '-[] ],
794 opt(Opt),
795 hint_help(M).
796opt_error(missing_value(Opt, Type)) -->
797 [ 'Option '-[] ],
798 opt(Opt),
799 [ ' requires an argument (of type ~p)'-[Type] ].
800opt_error(value_type(Opt, Type, Found)) -->
801 [ 'Option '-[] ],
802 opt(Opt), [' requires'],
803 type(Type),
804 [ ' (found '-[], ansi(code, '~w', [Found]), ')'-[] ].
805opt_error(access_file(File, exist)) -->
806 [ 'File '-[], ansi(code, '~w', [File]),
807 ' does not exist'-[]
808 ].
809opt_error(access_file(File, Access)) -->
810 { access_verb(Access, Verb) },
811 [ 'Cannot access file '-[], ansi(code, '~w', [File]),
812 ' for '-[], ansi(code, '~w', [Verb])
813 ].
814
815access_verb(read, reading).
816access_verb(write, writing).
817access_verb(append, writing).
818access_verb(execute, executing).
819
820hint_help(M) -->
821 { in(M:opt_type(Opt, help, boolean)) },
822 !,
823 [ ' (' ], opt(Opt), [' for help)'].
824hint_help(_) -->
825 [ ' (-h for help)'-[] ].
826
827opt(Opt) -->
828 { short_opt(Opt) },
829 !,
830 [ ansi(bold, '-~w', [Opt]) ].
831opt(Opt) -->
832 [ ansi(bold, '--~w', [Opt]) ].
833
834type(A|B) -->
835 type(A), [' or'],
836 type(B).
837type(oneof([One])) -->
838 !,
839 [ ' ' ],
840 atom(One).
841type(oneof(List)) -->
842 !,
843 [ ' one of '-[] ],
844 sequence(atom, [', '], List).
845type(between(Low, High)) -->
846 !,
847 [ ' a number '-[],
848 ansi(code, '~w', [Low]), '..', ansi(code, '~w', [High])
849 ].
850type(nonneg) -->
851 [ ' a non-negative integer'-[] ].
852type(natural) -->
853 [ ' a positive integer (>= 1)'-[] ].
854type(file(Access)) -->
855 [ ' a file with ~w access'-[Access] ].
856type(Type) -->
857 [ ' an argument of type '-[], ansi(code, '~w', [Type]) ].
858
859atom(A) -->
860 [ ansi(code, '~w', [A]) ].
861
862
863 866
882
883cli_parse_debug_options([], []).
884cli_parse_debug_options([H|T0], Opts) :-
885 debug_option(H),
886 !,
887 cli_parse_debug_options(T0, Opts).
888cli_parse_debug_options([H|T0], [H|T]) :-
889 cli_parse_debug_options(T0, T).
890
891debug_option(interactive(true)) :-
892 asserta(interactive).
893debug_option(debug(TopicS)) :-
894 term_string(Topic, TopicS),
895 debug(Topic).
896debug_option(spy(Atom)) :-
897 atom_pi(Atom, PI),
898 spy(PI).
899debug_option(gspy(Atom)) :-
900 atom_pi(Atom, PI),
901 tspy(PI).
902
903atom_pi(Atom, Module:PI) :-
904 split(Atom, :, Module, PiAtom),
905 !,
906 atom_pi(PiAtom, PI).
907atom_pi(Atom, Name//Arity) :-
908 split(Atom, //, Name, Arity),
909 !.
910atom_pi(Atom, Name/Arity) :-
911 split(Atom, /, Name, Arity),
912 !.
913atom_pi(Atom, _) :-
914 format(user_error, 'Invalid predicate indicator: "~w"~n', [Atom]),
915 halt(1).
916
917split(Atom, Sep, Before, After) :-
918 sub_atom(Atom, BL, _, AL, Sep),
919 !,
920 sub_atom(Atom, 0, BL, _, Before),
921 sub_atom(Atom, _, AL, 0, AfterAtom),
922 ( atom_number(AfterAtom, After)
923 -> true
924 ; After = AfterAtom
925 ).
926
927
937
938cli_enable_development_system :-
939 on_signal(int, _, debug),
940 set_prolog_flag(xpce_threaded, true),
941 set_prolog_flag(message_ide, true),
942 ( current_prolog_flag(xpce_version, _)
943 -> use_module(library(pce_dispatch)),
944 memberchk(Goal, [pce_dispatch([])]),
945 call(Goal)
946 ; true
947 ),
948 set_prolog_flag(toplevel_goal, prolog).
949
950
951 954
955:- multifile
956 prolog:called_by/2. 957
958prolog:called_by(main, [main(_)]).
959prolog:called_by(argv_options(_,_,_),
960 [ opt_type(_,_,_),
961 opt_help(_,_),
962 opt_meta(_,_)
963 ])