1/*  Part of Extended Tools for SWI-Prolog
    2
    3    Author:        Edison Mera Menendez
    4    E-mail:        efmera@gmail.com
    5    WWW:           https://github.com/edisonm/xtools
    6    Copyright (C): 2015, Process Design Center, Breda, The Netherlands.
    7    All rights reserved.
    8
    9    Redistribution and use in source and binary forms, with or without
   10    modification, are permitted provided that the following conditions
   11    are met:
   12
   13    1. Redistributions of source code must retain the above copyright
   14       notice, this list of conditions and the following disclaimer.
   15
   16    2. Redistributions in binary form must reproduce the above copyright
   17       notice, this list of conditions and the following disclaimer in
   18       the documentation and/or other materials provided with the
   19       distribution.
   20
   21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   32    POSSIBILITY OF SUCH DAMAGE.
   33*/
   34
   35:- module(option_utils, [select_option_default/3,
   36                         option_module_files/2,
   37                         option_module_files/3,
   38                         option_files/2,
   39                         option_dirs/2,
   40                         source_extension/2,
   41                         check_dir_file/3,
   42                         check_pred/2,
   43                         check_module/2
   44                        ]).   45
   46:- reexport(library(module_files)).   47:- use_module(library(apply)).   48:- use_module(library(lists)).   49:- use_module(library(option)).   50:- use_module(library(pairs)).   51:- use_module(library(solution_sequences)).   52:- use_module(library(from_utils)).   53:- use_module(library(implemented_in_base)).   54
   55:- multifile user:prolog_file_type/2.   56:- dynamic   user:prolog_file_type/2.   57
   58% TBD: This module requires optimization
   59
   60select_option_default(Holder-Default, Options1, Options) :-
   61    select_option(Holder, Options1, Options, Default).
   62
   63curr_alias_file(AliasL, EL, Loaded, File, Options1) :-
   64    member(Alias, AliasL),
   65    ( EL = (-)
   66    ->Options = Options1
   67    ; Options = [extensions([''|EL])|Options1]
   68    ),
   69    absolute_file_name(Alias, Pattern, [solutions(all)|Options]),
   70    expand_file_name(Pattern, FileL),
   71    member(File, FileL),
   72    check_file(Loaded, File).
   73
   74alias_files(AliasL, EL, Loaded, FileL, Options) :-
   75    findall(File, curr_alias_file(AliasL, EL, Loaded, File, Options), FileL).
   76
   77check_file(true, File) :- access_file(File, exist).  % exist checked at the end to avoid premature fail
   78check_file(loaded,  _).
   79
   80:- table module_file_/2 as subsumptive.   81
   82module_file_(M, F) :- module_file(M, F).
   83
   84check_module(Module, File) :-
   85    distinct(File, module_file_(Module, File)).
   86
   87source_extension(Type, Ext) :-
   88    user:prolog_file_type(Ext, Type),
   89    ( Type \= prolog
   90    ->true
   91    ; \+ user:prolog_file_type(Ext, qlf)
   92    ).
   93
   94% Based on predicate with same name in prolog_source.pl:
   95
   96src_files([], _, _) --> [].
   97src_files([H|T], Dir, Options1) -->
   98    { \+ special(H),
   99      file_name_extension(_, Ext, H),
  100      select_option(extensions(ExtL), Options1, Options, []),
  101      ( ExtL \= []
  102      ->memberchk(Ext, ExtL)
  103      ; option(file_type(Type), Options, prolog)
  104      ->once(source_extension(Type, Ext))
  105      ),
  106      directory_file_path(Dir, H, File1),
  107      absolute_file_name(File1, File,
  108                         [ file_errors(fail)
  109                         | Options
  110                         ])
  111    },
  112    !,
  113    [File],
  114    src_files(T, Dir, Options1).
  115src_files([H|T], Dir, Options) -->
  116    { \+ special(H),
  117      option(recursive(true), Options),
  118      directory_file_path(Dir, H, SubDir),
  119      exists_directory(SubDir),
  120      !,
  121      catch(directory_files(SubDir, Files), _, fail)
  122    },
  123    !,
  124    src_files(Files, SubDir, Options),
  125    src_files(T, Dir, Options).
  126src_files([_|T], Dir, Options) -->
  127    src_files(T, Dir, Options).
  128
  129special(.).
  130special(..).
  131
  132process_files(File, OFile, Options1) :-
  133    EL = OFile.extensions,
  134    Loaded = OFile.if,
  135    Files = OFile.files,
  136    AFile = OFile.file,
  137    merge_options(Options1, [file_type(prolog)], Options),
  138    ( ( nonvar(Files)
  139      ->( nonvar(AFile)
  140        ->flatten([AFile|Files], AliasL)
  141        ; AFile = File,
  142          flatten(Files, AliasL)
  143        )
  144      ; nonvar(AFile)
  145      ->AliasL = [AFile]
  146      )
  147    ->alias_files(AliasL, EL, Loaded, FileL, Options),
  148      member(File, FileL)
  149    ; AFile = File
  150    ).
  151
  152process_exclude_files(ExFileL, OFile, Options1) :-
  153    merge_options(Options1, [file_type(prolog)], Options),
  154    EL = OFile.extensions,
  155    Loaded = OFile.if,
  156    AExFileL = OFile.exclude_files,
  157    alias_files(AExFileL, EL, Loaded, ExFileL, Options).
  158
  159process_exclude_fdirs(ExDirL, OFile, Options1) :-
  160    ExADirL = OFile.exclude_dirs,
  161    merge_options([file_type(directory)], Options1, Options),
  162    alias_files(ExADirL, -, true, ExDirL, Options).
  163
  164process_fdirs(File, OFile, Options1) :-
  165    Loaded = OFile.if,
  166    Dirs = OFile.dirs,
  167    ADir = OFile.dir,
  168    merge_options([file_type(directory)], Options1, Options),
  169    ( ( nonvar(Dirs)
  170      ->( nonvar(ADir)
  171        ->flatten([ADir|Dirs], ADirL)
  172        ; ADir = Dir,
  173          flatten(Dirs, ADirL)
  174        )
  175      ; nonvar(ADir)
  176      ->ADirL = [ADir]
  177      )
  178    ->alias_files(ADirL, -, true, DirL, Options),
  179      ( Loaded = true
  180      ->EL = OFile.extensions,
  181        ( EL = (-)
  182        ->Options2 = Options1
  183        ; Options2 = [extensions(EL)|Options1]
  184        ),
  185        Params = source(Options2)
  186      ; findall(F, module_file_(_, F), LFileU),
  187        sort(LFileU, LFileL),
  188        Params = loaded(LFileL)
  189      ),
  190      member(Dir, DirL),
  191      check_dir_file(Params, Dir, File)
  192    ; true
  193    ).
  194
  195% here, we need all the files, even if the option specifies only loaded
  196% files, otherwise included files without clauses will be ignored
  197% directory_source_files(Dir, FileL, [recursive(true), if(false)]),
  198
  199check_dir_file(source(Options), Dir, File) :-
  200    directory_files(Dir, Files),
  201    phrase(src_files(Files, Dir, [recursive(true)|Options]), FileU),
  202    sort(FileU, FileL),
  203    member(File, FileL).
  204
  205check_dir_file(loaded(FileL), Dir, File) :-
  206    % Note: here we can not use distinct(File, module_file(_, File)) because
  207    % that will be too slow, instead we findall and deduplicate via sort/2
  208    member(File, FileL),
  209    directory_file_path(Dir, _, File).
  210
  211option_exclude_dirs(EL, DirL, Options1, Options2) :-
  212    foldl(select_option_default,
  213	 [exclude_dirs(ExDirL)-[]
  214	 ], Options1, Options2),
  215    merge_options([file_type(directory)], Options2, Options),
  216    alias_files(ExDirL, EL, true, DirL, Options).
  217
  218option_dirs(EL, Dir, Options1, Options2) :-
  219    foldl(select_option_default,
  220	  [dirs(Dirs)-Dirs,
  221	   dir( ADir)-ADir
  222	  ], Options1, Options2),
  223    merge_options([file_type(directory)], Options2, Options),
  224    ( nonvar(Dirs)
  225    ->( nonvar(ADir)
  226      ->flatten([ADir|Dirs], ADirL)
  227      ; ADir = Dir,
  228        flatten(Dirs, ADirL)
  229      )
  230    ; nonvar(ADir)
  231    ->ADirL = [ADir]
  232    ),
  233    alias_files(ADirL, EL, true, DirL, Options),
  234    member(Dir, DirL).
  235
  236check_pred(Head, File) :-
  237    implemented_in(Head, From, _),
  238    from_to_file(From, File).
  239
  240process_preds(File, OFile) :-
  241    HeadL = OFile.preds,
  242    ( is_list(HeadL)
  243    ->member(Head, HeadL),
  244      check_pred(Head, File)
  245    ; nonvar(HeadL)
  246    ->check_pred(HeadL, File)
  247    ; true
  248    ).
  249
  250option_file(M, File, OFile, Options) :-
  251    process_exclude_files(ExFileL, OFile, Options),
  252    process_exclude_fdirs(ExDirL,  OFile, Options),
  253    process_fdirs(File, OFile, Options),
  254    process_preds(File, OFile),
  255    process_files(File, OFile, Options),
  256    \+ member(File, ExFileL),
  257    \+ ( member(ExDir, ExDirL),
  258         directory_file_path(ExDir, _, File)
  259       ),
  260    ( ML \= []
  261    ->member(M, ML)
  262    ; true
  263    ),
  264    ( nonvar(M)
  265    ->module_file_(M, File)
  266    ; true = OFile.if,
  267      \+ ( var(M),
  268           var(File)
  269         )
  270    ->ignore(( module_file_(M, File)
  271             ; M = (-)
  272             ))
  273    ; var(File)
  274    ->module_file_(M, File)
  275    ; once(module_file_(M, File))
  276    ),
  277    ( Prop \= []
  278    ->module_property(M, Prop)
  279    ; true
  280    ).
  281
  282option_dir(Dir) -->
  283    foldl(select_option_default,
  284          [extensions(EL)-(-)
  285          ]),
  286    option_exclude_dirs(EL, ExDirL),
  287    option_dirs(EL, Dir),
  288    {\+ member(Dir, ExDirL)}.
  289
  290% The empty list doesn't mean anything, is just to avoid usage of a variable
  291to_nv(Name, Name=[]).
  292
  293pair_nv(M-FileL, M=FileD) :-
  294    list_dict(FileL, file, FileD).
  295
  296collect_elem(file,  _, File, File).
  297collect_elem(mfile, M, File, M-File).
  298
  299collect_dict(file, FileL, FileD) :-
  300    list_dict(FileL, file, FileD).
  301collect_dict(mfile, MFileU, MFileD) :-
  302    keysort(MFileU, MFileS),
  303    group_pairs_by_key(MFileS, MFileL),
  304    maplist(pair_nv, MFileL, MFileNV),
  305    dict_create(MFileD, mfile, MFileNV).
  306
  307project_dict(file,  M, MFileD, FileD) :-
  308    findall(File,
  309              ( get_dict(M, MFileD, FileD1),
  310                get_dict(File, FileD1, _)
  311              ), FileL),
  312    list_dict(FileL, file, FileD).
  313project_dict(mfile, _, MFileD, MFileD).
  314
  315option_collect(Name, Dict, Options1, Options) :-
  316    foldl(select_option_default,
  317          [module_files(MFileD)-(-),
  318           if(Loaded)-true,
  319           module_property(Prop)-[],
  320           module(M)-M,
  321           modules(ML)-[],
  322           extensions(EL)-(-),
  323           exclude_files(AExFileL)-[],
  324           exclude_dirs(ExADirL)-[],
  325           dirs(Dirs)-Dirs,
  326	   dir( ADir)-ADir,
  327           preds(HeadL)-HeadL,
  328           files(Files)-Files,
  329	   file( AFile)-AFile
  330          ], Options1, Options),
  331    ( MFileD == (-)
  332    ->OFile = ofile{if:Loaded,
  333                    module_property:Prop,
  334                    modules:ML,
  335                    extensions:EL,
  336                    exclude_files:AExFileL,
  337                    exclude_dirs:ExADirL,
  338                    dirs:Dirs,
  339                    dir:ADir,
  340                    preds:HeadL,
  341                    files:Files,
  342                    file:AFile},
  343      findall(Elem,
  344              ( option_file(M, File, OFile, Options),
  345                collect_elem(Name, M, File, Elem)
  346              ), ElemL),
  347      collect_dict(Name, ElemL, Dict)
  348    ; project_dict(Name, M, MFileD, Dict)
  349    ).
  350
  351option_module_files(Options, MFileD) :-
  352    option_module_files(MFileD, Options, _).
  353
  354
  355option_module_files(MFileD, Options1, Options) :-
  356    option_collect(mfile, MFileD, Options1, Options).
  357
  358option_files(Options, FileD) :-
  359    option_files(FileD, Options, _).
  360
  361option_files(FileD, Options1, Options) :-
  362    option_collect(file, FileD, Options1, Options).
  363
  364list_dict(ElemU, Key, ElemD) :-
  365    sort(ElemU, ElemL),
  366    maplist(to_nv, ElemL, ElemKVL),
  367    dict_create(ElemD, Key, ElemKVL).
  368
  369option_dirs(Options, DirD) :-
  370    findall(Dir, option_dir(Dir, Options, _), DirU),
  371    list_dict(DirU, dir, DirD)