View source with formatted 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)  1985-2018, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(shell,
   38          [ shell/0,
   39            ls/0,
   40            ls/1,                               % +Pattern
   41            cd/0,
   42            cd/1,                               % +Dir
   43            pushd/0,
   44            pushd/1,                            % +Dir
   45            dirs/0,
   46            pwd/0,
   47            popd/0,
   48            mv/2,                               % +File1, +File2
   49            rm/1                                % +File1
   50          ]).   51:- use_module(library(lists), [nth1/3]).   52:- use_module(library(error)).   53:- use_module(library(apply)).   54:- set_prolog_flag(generate_debug_info, false).   55
   56/** <module>  Elementary shell commands
   57
   58This library provides some  basic  (POSIX)   shell  commands  defined in
   59Prolog, such as `pwd` and `ls` for   situations  where there is no shell
   60available or the shell output cannot be captured.
   61*/
   62
   63%!  shell
   64%
   65%   Execute an interactive shell. The  following   options  are tried to
   66%   find a suitable shell command:
   67%
   68%     1. The Prolog flag `shell`
   69%     2. The environment variable ``$SHELL``
   70%     3. The Prolog flag `posix_shell`
   71%     4. The environment variable ``%comspec%`` (Windows only)
   72%
   73%   @error existence_error(config, shell) if no suitable shell can be
   74%   found.
   75
   76shell :-
   77    interective_shell(Shell),
   78    access_file(Shell, execute),
   79    !,
   80    shell(Shell).
   81shell :-
   82    existence_error(config, shell).
   83
   84interective_shell(Shell) :-
   85    current_prolog_flag(shell, Shell).
   86interective_shell(Shell) :-
   87    getenv('SHELL', Shell).
   88interective_shell(Shell) :-
   89    current_prolog_flag(posix_shell, Shell).
   90interective_shell(Shell) :-
   91    current_prolog_flag(windows, true),
   92    getenv(comspec, Shell).             % Windows
   93
   94
   95%!  cd.
   96%!  cd(Dir).
   97%
   98%   Change working directory
   99
  100cd :-
  101    cd(~).
  102
  103cd(Dir) :-
  104    name_to_file(Dir, Name),
  105    working_directory(_, Name).
  106
  107%!  pushd.
  108%!  pushd(+Dir).
  109%!  popd.
  110%!  dirs.
  111%
  112%   Manage the _directory stack_:
  113%
  114%     - pushd/1 is as cd/1, pushing th old directory on a stack
  115%     - pushd/0 swaps the current directory with the top of the
  116%       stack
  117%     - popd/0 pops to the top of the stack
  118%     - dirs/0 lists the current directory and the stack.
  119
  120:- dynamic
  121    stack/1.  122
  123pushd :-
  124    pushd(+1).
  125
  126pushd(N) :-
  127    integer(N),
  128    !,
  129    findall(D, stack(D), Ds),
  130    (   nth1(N, Ds, Go),
  131        retract(stack(Go))
  132    ->  pushd(Go),
  133        print_message(information, shell(directory(Go)))
  134    ;   warning('Directory stack not that deep', []),
  135        fail
  136    ).
  137pushd(Dir) :-
  138    name_to_file(Dir, Name),
  139    working_directory(Old, Name),
  140    asserta(stack(Old)).
  141
  142popd :-
  143    retract(stack(Dir)),
  144    !,
  145    working_directory(_, Dir),
  146    print_message(information, shell(directory(Dir))).
  147popd :-
  148    warning('Directory stack empty', []),
  149    fail.
  150
  151dirs :-
  152    working_directory(WD, WD),
  153    findall(D, stack(D), Dirs),
  154    maplist(dir_name, [WD|Dirs], Results),
  155    print_message(information, shell(file_set(Results))).
  156
  157%!  pwd
  158%
  159%   Print current working directory
  160
  161pwd :-
  162    working_directory(WD, WD),
  163    print_message(information, format('~w', [WD])).
  164
  165dir_name('/', '/') :- !.
  166dir_name(Path, Name) :-
  167    atom_concat(P, /, Path),
  168    !,
  169    dir_name(P, Name).
  170dir_name(Path, Name) :-
  171    current_prolog_flag(unix, true),
  172    expand_file_name('~', [Home0]),
  173    (   atom_concat(Home, /, Home0)
  174    ->  true
  175    ;   Home = Home0
  176    ),
  177    atom_concat(Home, FromHome, Path),
  178    !,
  179    atom_concat('~', FromHome, Name).
  180dir_name(Path, Path).
  181
  182%!  ls.
  183%!  ls(+Pattern).
  184%
  185%   Listing similar to Unix =ls -F=, flagging directories with =/=.
  186
  187ls :-
  188    ls('.').
  189
  190ls(Spec) :-
  191    name_to_files(Spec, Matches),
  192    ls_(Matches).
  193
  194ls_([]) :-
  195    !,
  196    warning('No Match', []).
  197ls_([Dir]) :-
  198    exists_directory(Dir),
  199    !,
  200    atom_concat(Dir, '/*', Pattern),
  201    expand_file_name(Pattern, Files),
  202    maplist(tagged_file_in_dir, Files, Results),
  203    print_message(information, shell(file_set(Results))).
  204ls_(Files) :-
  205    maplist(tag_file, Files, Results),
  206    print_message(information, shell(file_set(Results))).
  207
  208tagged_file_in_dir(File, Result) :-
  209    file_base_name(File, Base),
  210    (   exists_directory(File)
  211    ->  atom_concat(Base, /, Result)
  212    ;   Result = Base
  213    ).
  214
  215tag_file(File, Dir) :-
  216    exists_directory(File),
  217    !,
  218    atom_concat(File, /, Dir).
  219tag_file(File, File).
  220
  221%!  mv(+From, +To) is det.
  222%
  223%   Move (Rename) a file. If To is   a directory, From is moved into
  224%   the directory.  Uses expand_file_name/2 on the From argument.
  225
  226mv(From, To) :-
  227    name_to_files(From, Src),
  228    name_to_new_file(To, Dest),
  229    mv_(Src, Dest).
  230
  231mv_([One], Dest) :-
  232    \+ exists_directory(Dest),
  233    !,
  234    rename_file(One, Dest).
  235mv_(Multi, Dest) :-
  236    (   exists_directory(Dest)
  237    ->  maplist(mv_to_dir(Dest), Multi)
  238    ;   print_message(warning, format('Not a directory: ~w', [Dest])),
  239        fail
  240    ).
  241
  242mv_to_dir(Dest, Src) :-
  243    file_base_name(Src, Name),
  244    atomic_list_concat([Dest, Name], /, Target),
  245    rename_file(Src, Target).
  246
  247%!  rm(+File) is det.
  248%
  249%   Remove (unlink) a file
  250
  251rm(File) :-
  252    name_to_file(File, A),
  253    delete_file(A).
  254
  255
  256%!  name_to_file(+Name, -File)
  257%
  258%   Convert Name into a single file.
  259
  260name_to_file(Spec, File) :-
  261    name_to_files(Spec, Files),
  262    (   Files = [File]
  263    ->  true
  264    ;   print_message(warning, format('Ambiguous: ~w', [Spec])),
  265        fail
  266    ).
  267
  268name_to_new_file(Spec, File) :-
  269    name_to_files(Spec, Files, false),
  270    (   Files = [File]
  271    ->  true
  272    ;   print_message(warning, format('Ambiguous: ~w', [Spec])),
  273        fail
  274    ).
  275
  276name_to_files(Spec, Files) :-
  277    name_to_files(Spec, Files, true).
  278name_to_files(Spec, Files, Exists) :-
  279    name_to_files_(Spec, Files, Exists),
  280    (   Files == []
  281    ->  print_message(warning, format('No match: ~w', [Spec])),
  282        fail
  283    ;   true
  284    ).
  285
  286name_to_files_(Spec, Files, _) :-
  287    compound(Spec),
  288    compound_name_arity(Spec, _Alias, 1),
  289    !,
  290    findall(File,
  291            (   absolute_file_name(Spec, File,
  292                                   [ access(exist),
  293                                     file_type(directory),
  294                                     file_errors(fail),
  295                                     solutions(all)
  296                                   ])
  297            ;   absolute_file_name(Spec, File,
  298                                   [ access(exist),
  299                                     file_errors(fail),
  300                                     solutions(all)
  301                                   ])
  302            ),
  303            Files).
  304name_to_files_(Spec, Files, Exists) :-
  305    file_name_to_atom(Spec, S1),
  306    expand_file_name(S1, Files0),
  307    (   Exists == true,
  308        Files0 == [S1],
  309        \+ access_file(S1, exist)
  310    ->  warning('"~w" does not exist', [S1]),
  311        fail
  312    ;   Files = Files0
  313    ).
  314
  315file_name_to_atom(Spec, File) :-
  316    atomic(Spec),
  317    !,
  318    atom_string(File, Spec).
  319file_name_to_atom(Spec, File) :-
  320    phrase(segments(Spec), L),
  321    atomic_list_concat(L, /, File).
  322
  323segments(Var) -->
  324    { var(Var),
  325      !,
  326      instantiation_error(Var)
  327    }.
  328segments(A/B) -->
  329    !,
  330    segments(A),
  331    segments(B).
  332segments(A) -->
  333    { must_be(atomic, A) },
  334    [ A ].
  335
  336%!  warning(+Fmt, +Args:list) is det.
  337
  338warning(Fmt, Args) :-
  339    print_message(warning, format(Fmt, Args)).
  340
  341:- multifile prolog:message//1.  342
  343prolog:message(shell(file_set(Files))) -->
  344    { catch(tty_size(_, Width), _, Width = 80)
  345    },
  346    table(Files, Width).
  347prolog:message(shell(directory(Path))) -->
  348    { dir_name(Path, Name) },
  349    [ '~w'-[Name] ].
  350
  351%!  table(+List, +Width)//
  352%
  353%   Produce a tabular layout to list all   elements of List on lines
  354%   with a maximum width of Width. Elements are placed as =ls= does:
  355%
  356%      ==
  357%      1  4  7
  358%      2  5  8
  359%      3  6
  360%      ==
  361
  362table(List, Width) -->
  363    { table_layout(List, Width, Layout),
  364      compound_name_arguments(Array, a, List)
  365    },
  366    table(0, Array, Layout).
  367
  368table(I, Array, Layout) -->
  369    { Cols = Layout.cols,
  370      Index is I // Cols + (I mod Cols) * Layout.rows + 1,
  371      (   (I+1) mod Cols =:= 0
  372      ->  NL = true
  373      ;   NL = false
  374      )
  375    },
  376    (   { arg(Index, Array, Atom) }
  377    ->  (   { NL == false }
  378        ->  [ '~|~w~t~*+'-[Atom, Layout.col_width] ]
  379        ;   [ '~w'-[Atom] ]
  380        )
  381    ;   []
  382    ),
  383    (   { I2 is I+1,
  384          I2 < Cols*Layout.rows
  385        }
  386    ->  (   { NL == true }
  387        ->  [ nl ]
  388        ;   []
  389        ),
  390        table(I2, Array, Layout)
  391    ;   []
  392    ).
  393
  394table_layout(Atoms, Width, _{cols:Cols, rows:Rows, col_width:ColWidth}) :-
  395    length(Atoms, L),
  396    longest(Atoms, Longest),
  397    Cols is max(1, Width // (Longest + 3)),
  398    Rows is integer(L / Cols + 0.49999),    % should be ceil/1
  399    ColWidth is Width // Cols.
  400
  401longest(List, Longest) :-
  402    longest(List, 0, Longest).
  403
  404longest([], M, M) :- !.
  405longest([H|T], Sofar, M) :-
  406    atom_length(H, L),
  407    L >= Sofar,
  408    !,
  409    longest(T, L, M).
  410longest([_|T], S, M) :-
  411    longest(T, S, M)