1#############################################################################
2##
3##  This file is part of GAP, a system for computational discrete algebra.
4##  This file's authors include Frank Celler, Alexander Hulpke.
5##
6##  Copyright of GAP belongs to its developers, whose names are too numerous
7##  to list here. Please refer to the COPYRIGHT file for details.
8##
9##  SPDX-License-Identifier: GPL-2.0-or-later
10##
11##  This file contains support for ⪆ packages.
12##
13
14
15# recode string to GAPInfo.TermEncoding, assuming input is UTF-8 or latin1
16# (if useful this may become documented for general use)
17BindGlobal( "RecodeForCurrentTerminal", function( str )
18    local fun, u;
19    if IsBoundGlobal( "Unicode" ) and IsBoundGlobal( "Encode" ) then
20      # The GAPDoc package is completely loaded.
21      fun:= ValueGlobal( "Unicode" );
22      u:= fun( str, "UTF-8" );
23      if u = fail then
24        u:= fun( str, "ISO-8859-1");
25      fi;
26      if GAPInfo.TermEncoding <> "UTF-8" then
27        fun:= ValueGlobal( "SimplifiedUnicodeString" );
28        u:= fun( u, GAPInfo.TermEncoding );
29      fi;
30      fun:= ValueGlobal( "Encode" );
31      u:= fun( u, GAPInfo.TermEncoding );
32      return u;
33    else
34      # GAPDoc is not yet available, do nothing in this case.
35      return str;
36    fi;
37  end );
38
39#############################################################################
40##
41#F  CompareVersionNumbers( <supplied>, <required>[, "equal"] )
42##
43InstallGlobalFunction( CompareVersionNumbers, function( arg )
44    local s, r, inequal, i, j, a, b;
45
46    s:= arg[1];
47    r:= arg[2];
48    inequal:= not ( Length( arg ) = 3 and arg[3] = "equal" );
49
50    # Deal with the case of a `dev' version.
51    if   2 < Length( s )
52       and s{ [ Length( s ) - 2 .. Length( s ) ] } = "dev" then
53      return inequal or ( Length(r)>2 and r{[Length(r)-2..Length(r)]}="dev" );
54    elif 2 < Length( r )
55       and r{ [ Length( r ) - 2 .. Length( r ) ] } = "dev" then
56      return false;
57    fi;
58
59    while 0 < Length( s ) or 0 < Length( r ) do
60
61      # Remove leading non-digit characters.
62      i:= 1;
63      while i <= Length( s ) and not IsDigitChar( s[i] ) do
64        i:= i+1;
65      od;
66      s:= s{ [ i .. Length( s ) ] };
67      j:= 1;
68      while j <= Length( r ) and not IsDigitChar( r[j] ) do
69        j:= j+1;
70      od;
71      r:= r{ [ j .. Length( r ) ] };
72
73      # If one of the two strings is empty then we are done.
74      if   Length( s ) = 0 then
75        return Length( r ) = 0;
76      elif Length( r ) = 0 then
77        return inequal;
78      fi;
79
80      # Compare the next portion of digit characters.
81      i:= 1;
82      while i <= Length( s ) and IsDigitChar( s[i] ) do
83        i:= i+1;
84      od;
85      a:= Int( s{ [ 1 .. i-1 ] } );
86      j:= 1;
87      while j <= Length( r ) and IsDigitChar( r[j] ) do
88        j:= j+1;
89      od;
90      b:= Int( r{ [ 1 .. j-1 ] } );
91      if   a < b then
92        return false;
93      elif b < a then
94        return inequal;
95      fi;
96      s:= s{ [ i .. Length( s ) ] };
97      r:= r{ [ j .. Length( r ) ] };
98
99    od;
100
101    # The two remaining strings are empty.
102    return true;
103end );
104
105
106#############################################################################
107##
108#F  PackageInfo( <pkgname> )
109##
110InstallGlobalFunction( PackageInfo, function( pkgname )
111    pkgname:= LowercaseString( pkgname );
112    if not IsBound( GAPInfo.PackagesInfo.( pkgname ) ) then
113      return [];
114    else
115      return GAPInfo.PackagesInfo.( pkgname );
116    fi;
117    end );
118
119
120#############################################################################
121##
122#F  RECORDS_FILE( <name> )
123##
124InstallGlobalFunction( RECORDS_FILE, function( name )
125    local str, rows, recs, pos, r;
126
127    str:= StringFile( name );
128    if str = fail then
129      return [];
130    fi;
131    rows:= SplitString( str, "", "\n" );
132    recs:= [];
133    for r in rows do
134      # remove comments starting with `#'
135      pos:= Position( r, '#' );
136      if pos <> fail then
137        r:= r{ [ 1 .. pos-1 ] };
138      fi;
139      Append( recs, SplitString( r, "", " \n\t\r" ) );
140    od;
141    return List( recs, LowercaseString );
142    end );
143
144
145#############################################################################
146##
147#F  SetPackageInfo( <record> )
148##
149InstallGlobalFunction( SetPackageInfo, function( record )
150    local rnam, info;
151    if IsHPCGAP then
152        info := rec();
153        for rnam in REC_NAMES(record) do
154          info.(rnam) := Immutable(record.(rnam));
155        od;
156        record := info;
157    fi;
158    GAPInfo.PackageInfoCurrent:= record;
159    end );
160
161
162#############################################################################
163##
164#F  FindPackageInfosInSubdirectories( pkgdir, name )
165##
166##  Finds all PackageInfos in subdirectories of directory name in
167##  directory pkgdir, return a list of their paths.
168##
169BindGlobal( "FindPackageInfosInSubdirectories", function( pkgdir, name )
170    local pkgpath, file, files, subdir;
171    pkgpath:= Filename( [ pkgdir ], name );
172    # This can be 'fail' if 'name' is a void link.
173    if pkgpath = fail then
174      return [];
175    fi;
176
177    if not IsDirectoryPath( pkgpath ) then
178      return [];
179    fi;
180    if name in [ ".", ".." ] then
181      return [];
182    fi;
183
184    file:= Filename( [ pkgdir ],
185                      Concatenation( name, "/PackageInfo.g" ) );
186    if file = fail then
187      files := [];
188      # Perhaps some subdirectories contain `PackageInfo.g' files.
189      for subdir in Set( DirectoryContents( pkgpath ) ) do
190        if not subdir in [ ".", ".." ] then
191          pkgpath:= Filename( [ pkgdir ],
192                              Concatenation( name, "/", subdir ) );
193          if pkgpath <> fail and IsDirectoryPath( pkgpath )
194                              and not subdir in [ ".", ".." ] then
195            file:= Filename( [ pkgdir ],
196                Concatenation( name, "/", subdir, "/PackageInfo.g" ) );
197            if file <> fail then
198              Add( files,
199                    [ file, Concatenation( name, "/", subdir ) ] );
200            fi;
201          fi;
202        fi;
203      od;
204    else
205      files:= [ [ file, name ] ];
206    fi;
207    return files;
208end );
209
210
211#############################################################################
212##
213#F  AddPackageInfo( files )
214##
215BindGlobal( "AddPackageInfos", function( files, pkgdir, ignore )
216    local file, record, pkgname, version;
217    for file in files do
218      # Read the `PackageInfo.g' file.
219      Unbind( GAPInfo.PackageInfoCurrent );
220      Read( file[1] );
221      if IsBound( GAPInfo.PackageInfoCurrent ) then
222        record:= GAPInfo.PackageInfoCurrent;
223        Unbind( GAPInfo.PackageInfoCurrent );
224        pkgname:= LowercaseString( record.PackageName );
225        NormalizeWhitespace( pkgname );
226        version:= record.Version;
227
228        # If we have this version already then leave it out.
229        if ForAll( GAPInfo.PackagesInfo,
230                    r ->    r.PackageName <> record.PackageName
231                        or r.Version <> version ) then
232
233          # Check whether GAP wants to reset loadability.
234          if     IsBound( GAPInfo.PackagesRestrictions.( pkgname ) )
235              and GAPInfo.PackagesRestrictions.( pkgname ).OnInitialization(
236                      record ) = false then
237            Add( GAPInfo.PackagesInfoRefuseLoad, record );
238          elif pkgname in ignore then
239            LogPackageLoadingMessage( PACKAGE_DEBUG,
240                Concatenation( "ignore package ", record.PackageName,
241                " (user preference PackagesToIgnore)" ), "GAP" );
242          else
243            record.InstallationPath:= Filename( [ pkgdir ], file[2] );
244            if not IsBound( record.PackageDoc ) then
245              record.PackageDoc:= [];
246            elif IsRecord( record.PackageDoc ) then
247              record.PackageDoc:= [ record.PackageDoc ];
248            fi;
249            if IsHPCGAP then
250              # FIXME: we make the package info record immutable, to
251              # allow access from multiple threads; but that in turn
252              # can break packages, which rely on their package info
253              # record being readable (see issue #2568)
254              MakeImmutable(record);
255            fi;
256            Add( GAPInfo.PackagesInfo, record );
257          fi;
258        fi;
259      fi;
260    od;
261end );
262
263#############################################################################
264##
265#F  InitializePackagesInfoRecords()
266##
267##  In earlier versions, this function had an argument; now we ignore it.
268##
269InstallGlobalFunction( InitializePackagesInfoRecords, function( arg )
270    local dirs, pkgdirs, pkgdir, ignore, names, noauto, name, pkgpath,
271          file, files, subdir, str, record, r, pkgname, version;
272
273    if IsBound( GAPInfo.PackagesInfoInitialized ) and
274       GAPInfo.PackagesInfoInitialized = true then
275      # This function has already been executed in this sesion.
276      return;
277    fi;
278
279    GAPInfo.LoadPackageLevel:= 0;
280    GAPInfo.PackagesInfo:= [];
281    GAPInfo.PackagesInfoRefuseLoad:= [];
282
283    LogPackageLoadingMessage( PACKAGE_DEBUG,
284        "entering InitializePackagesInfoRecords", "GAP" );
285    dirs:= [];
286    pkgdirs:= DirectoriesLibrary( "pkg" );
287    if pkgdirs = fail then
288      LogPackageLoadingMessage( PACKAGE_DEBUG,
289          "exit InitializePackagesInfoRecords (no pkg directories found)",
290          "GAP" );
291      GAPInfo.PackagesInfo:= AtomicRecord();
292      return;
293    fi;
294
295    if IsBound( GAPInfo.ExcludeFromAutoload ) then
296      # The function was called from `AutoloadPackages'.
297      # Collect the `NOAUTO' information in a global list,
298      # which will be used in `AutoloadPackages' for both packages to be
299      # loaded according to the user preference "PackagesToLoad"
300      # and suggested packages of needed packages.
301      # The component `GAPInfo.ExcludeFromAutoload' will be unbound after the
302      # call of `AutoloadPackages'.
303      GAPInfo.ExcludeFromAutoload:= Set( List(
304          UserPreference( "ExcludeFromAutoload" ), LowercaseString ) );
305    fi;
306
307    # Do not store information about packages in "PackagesToIgnore".
308    ignore:= List( UserPreference( "PackagesToIgnore" ), LowercaseString );
309
310    # Loop over the package directories,
311    # remove the packages listed in `NOAUTO' files from GAP's suggested
312    # packages, and unite the information for the directories.
313    for pkgdir in pkgdirs do
314
315      if IsBound( GAPInfo.ExcludeFromAutoload ) then
316        UniteSet( GAPInfo.ExcludeFromAutoload,
317                  List( RECORDS_FILE( Filename( pkgdir, "NOAUTO" ) ),
318                        LowercaseString ) );
319      fi;
320
321      # Loop over subdirectories of this package directory.
322      for name in Set( DirectoryContents( Filename( pkgdir, "" ) ) ) do
323
324          ## Get all package dirs
325          files := FindPackageInfosInSubdirectories( pkgdir, name );
326
327          AddPackageInfos( files, pkgdir, ignore );
328
329      od;
330    od;
331
332    # Sort the available info records by their version numbers.
333    SortParallel( List( GAPInfo.PackagesInfo, r -> r.Version ),
334                  GAPInfo.PackagesInfo,
335                  CompareVersionNumbers );
336
337    # Turn the lists into records.
338    record:= rec();
339    for r in GAPInfo.PackagesInfo do
340      name:= LowercaseString( r.PackageName );
341      if IsBound( record.( name ) ) then
342        record.( name ) := Concatenation( record.( name ), [ r ] );
343      else
344        record.( name ):= [ r ];
345      fi;
346      if IsHPCGAP then
347        # FIXME: we make the package info record immutable, to
348        # allow access from multiple threads; but that in turn
349        # can break packages, which rely on their package info
350        # record being readable (see issue #2568)
351        MakeImmutable( record.( name ) );
352      fi;
353    od;
354    GAPInfo.PackagesInfo:= AtomicRecord(record);
355
356    GAPInfo.PackagesInfoInitialized:= true;
357    LogPackageLoadingMessage( PACKAGE_DEBUG,
358        "return from InitializePackagesInfoRecords", "GAP" );
359    end );
360
361
362#############################################################################
363##
364#F  LinearOrderByPartialWeakOrder( <pairs>, <weights> )
365##
366##  The algorithm works with a directed graph
367##  whose vertices are subsets of the <M>c_i</M>
368##  and whose edges represent the given partial order.
369##  We start with one vertex for each <M>x_i</M> and each <M>y_i</M>
370##  from the input list, and draw an edge from <M>x_i</M> to <M>y_i</M>.
371##  Furthermore,
372##  we need a queue <M>Q</M> of the smallest vertices found up to now,
373##  and a stack <M>S</M> of the largest vertices found up to now;
374##  both <M>Q</M> and <M>S</M> are empty at the beginning.
375##  Now we add the vertices without predecessors to <M>Q</M> and remove the
376##  edges from these vertices until no more such vertex is found.
377##  Then we put the vertices without successors on <M>S</M> and remove the
378##  edges to these vertices until no more such vertex is found.
379##  If edges are left then each of them leads eventually into a cycle in the
380##  graph; we find a cycle and amalgamate it into a new vertex.
381##  Now we repeat the process until all edges have disappeared.
382##  Finally, the concatenation of <M>Q</M> and <M>S</M> gives us the sets
383##  <M>c_i</M>.
384##
385InstallGlobalFunction( LinearOrderByPartialWeakOrder,
386    function( pairs, weights )
387    local Q, S, Qw, Sw, F, pair, vx, vy, v, pos, candidates, minwght,
388          smallest, s, maxwght, largest, p, cycle, next, new;
389
390    # Initialize the queue and the stack.
391    Q:= [];
392    S:= [];
393    Qw:= [];
394    Sw:= [];
395
396    # Create a list of vertices according to the input.
397    F:= [];
398    for pair in Set( pairs ) do
399      if pair[1] <> pair[2] then
400        vx:= First( F, r -> r.keys[1] = pair[1] );
401        if vx = fail then
402          vx:= rec( keys:= [ pair[1] ], succ:= [], pred:= [] );
403          Add( F, vx );
404        fi;
405        vy:= First( F, r -> r.keys[1] = pair[2] );
406        if vy = fail then
407          vy:= rec( keys:= [ pair[2] ], succ:= [], pred:= [] );
408          Add( F, vy );
409        fi;
410        Add( vx.succ, vy );
411        Add( vy.pred, vx );
412      fi;
413    od;
414
415    # Assign the weights.
416    weights:= SortedList( weights );
417    for v in F do
418      pos:= PositionSorted( weights, v.keys );
419      if pos <= Length( weights ) and weights[ pos ][1] = v.keys[1] then
420        v.wght:= weights[ pos ][2];
421      else
422        v.wght:= 0;
423      fi;
424    od;
425
426    # While F contains a vertex, ...
427    while not IsEmpty( F ) do
428
429      # ... find the vertices in F without predecessors and add them to Q,
430      # remove the edges from these vertices,
431      # and remove these vertices from F.
432      candidates:= Filtered( F, v -> IsEmpty( v.pred ) );
433      if not IsEmpty( candidates ) then
434        minwght:= infinity;    # larger than all admissible weights
435        for v in candidates do
436          if v.wght < minwght then
437            minwght:= v.wght;
438            smallest:= [ v ];
439          elif v.wght = minwght then
440            Add( smallest, v );
441          fi;
442        od;
443        for v in smallest do
444          Add( Q, v.keys );
445          Add( Qw, v.wght );
446          for s in v.succ do
447            s.pred:= Filtered( s.pred, x -> not IsIdenticalObj( v, x ) );
448            if IsEmpty( s.pred )
449               and ForAll( smallest, x -> not IsIdenticalObj( s, x ) ) then
450              Add( smallest, s );
451            fi;
452          od;
453          pos:= PositionProperty( F, x -> IsIdenticalObj( v, x ) );
454          Unbind( F[ pos ] );
455          F:= Compacted( F );
456        od;
457      fi;
458
459      # Then find the vertices in F without successors and put them on S,
460      # remove the edges to these vertices,
461      # and remove these vertices from F.
462      candidates:= Filtered( F, v -> IsEmpty( v.succ ) );
463      if not IsEmpty( candidates ) then
464        maxwght:= -1;    # smaller than all admissible weights
465        for v in candidates do
466          if v.wght > maxwght then
467            maxwght:= v.wght;
468            largest:= [ v ];
469          elif v.wght = maxwght then
470            Add( largest, v );
471          fi;
472        od;
473        for v in largest do
474          Add( S, v.keys );
475          Add( Sw, v.wght );
476          for p in v.pred do
477            p.succ:= Filtered( p.succ, x -> not IsIdenticalObj( v, x ) );
478            if IsEmpty( p.succ )
479               and ForAll( largest, x -> not IsIdenticalObj( p, x ) ) then
480              Add( largest, p );
481            fi;
482          od;
483          pos:= PositionProperty( F, x -> IsIdenticalObj( v, x ) );
484          Unbind( F[ pos ] );
485          F:= Compacted( F );
486        od;
487      fi;
488
489      if not IsEmpty( F ) then
490        # Find a cycle in F.
491        # (Note that now any vertex has a successor,
492        # so we may start anywhere, and eventually get into a cycle.)
493        cycle:= [];
494        next:= F[1];
495        repeat
496          Add( cycle, next );
497          next:= next.succ[1];
498          pos:= PositionProperty( cycle, x -> IsIdenticalObj( x, next ) );
499        until pos <> fail;
500        cycle:= cycle{ [ pos .. Length( cycle ) ] };
501
502        # Replace the set of vertices in the cycle by a new vertex,
503        # replace all edges from/to a vertex outside the cycle
504        # to/from a vertex in the cycle by edges to/from the new vertex.
505        new:= rec( keys:= [], succ:= [], pred:= [],
506                   wght:= Maximum( List( cycle, v -> v.wght ) ) );
507        for v in cycle do
508          UniteSet( new.keys, v.keys );
509          for s in v.succ do
510            if ForAll( cycle, w -> not IsIdenticalObj( s, w ) ) then
511              if ForAll( new.succ, w -> not IsIdenticalObj( s, w ) ) then
512                Add( new.succ, s );
513              fi;
514              pos:= PositionProperty( s.pred, w -> IsIdenticalObj( v, w ) );
515              if ForAll( s.pred, w -> not IsIdenticalObj( new, w ) ) then
516                s.pred[ pos ]:= new;
517              else
518                Unbind( s.pred[ pos ] );
519                s.pred:= Compacted( s.pred );
520              fi;
521            fi;
522          od;
523          for p in v.pred do
524            if ForAll( cycle, w -> not IsIdenticalObj( p, w ) ) then
525              if ForAll( new.pred, w -> not IsIdenticalObj( p, w ) ) then
526                Add( new.pred, p );
527              fi;
528              pos:= PositionProperty( p.succ, w -> IsIdenticalObj( v, w ) );
529              if ForAll( p.succ, w -> not IsIdenticalObj( new, w ) ) then
530                p.succ[ pos ]:= new;
531              else
532                Unbind( p.succ[ pos ] );
533                p.succ:= Compacted( p.succ );
534              fi;
535            fi;
536          od;
537          pos:= PositionProperty( F, x -> IsIdenticalObj( v, x ) );
538          Unbind( F[ pos ] );
539          F:= Compacted( F );
540        od;
541        Add( F, new );
542      fi;
543
544    od;
545
546    # Now the whole input is distributed to Q and S.
547    return rec( cycles:= Concatenation( Q, Reversed( S ) ),
548                weights:= Concatenation( Qw, Reversed( Sw ) ) );
549    end );
550
551
552#############################################################################
553##
554#I  InfoPackageLoading
555##
556##  (We cannot do this in `package.gd'.)
557##
558DeclareInfoClass( "InfoPackageLoading" );
559
560
561#############################################################################
562##
563#F  LogPackageLoadingMessage( <severity>, <message>[, <name>] )
564##
565if not IsBound( TextAttr ) then
566  TextAttr:= "dummy";
567fi;
568#T needed? (decl. of GAPDoc is loaded before)
569
570InstallGlobalFunction( LogPackageLoadingMessage, function( arg )
571    local severity, message, currpkg, i;
572
573    severity:= arg[1];
574    message:= arg[2];
575    if Length( arg ) = 3 then
576      currpkg:= arg[3];
577    elif IsBound( GAPInfo.PackageCurrent ) then
578      # This happens inside availability tests.
579      currpkg:= GAPInfo.PackageCurrent.PackageName;
580    else
581      currpkg:= "(unknown package)";
582    fi;
583    if IsString( message ) then
584      message:= [ message ];
585    fi;
586    if severity <= PACKAGE_WARNING
587       and UserPreference("UseColorsInTerminal") = true
588       and IsBound( TextAttr )
589       and IsRecord( TextAttr ) then
590      if severity = PACKAGE_ERROR then
591        message:= List( message,
592            msg -> Concatenation( TextAttr.1, msg, TextAttr.reset ) );
593      else
594        message:= List( message,
595            msg -> Concatenation( TextAttr.4, msg, TextAttr.reset ) );
596      fi;
597    fi;
598    Add( GAPInfo.PackageLoadingMessages, [ currpkg, severity, message ] );
599    Info( InfoPackageLoading, severity, currpkg, ": ", message[1] );
600    for i in [ 2 .. Length( message ) ] do
601      Info( InfoPackageLoading, severity, List( currpkg, x -> ' ' ),
602            "  ", message[i] );
603    od;
604    end );
605
606if not IsReadOnlyGlobal( "TextAttr" ) then
607  Unbind( TextAttr );
608fi;
609
610
611#############################################################################
612##
613#F  DisplayPackageLoadingLog( [<severity>] )
614##
615InstallGlobalFunction( DisplayPackageLoadingLog, function( arg )
616    local severity, entry, message, i;
617
618    if Length( arg ) = 0 then
619      severity:= PACKAGE_WARNING;
620    else
621      severity:= arg[1];
622    fi;
623
624    for entry in GAPInfo.PackageLoadingMessages do
625      if severity >= entry[2] then
626        message:= entry[3];
627        Info( InfoPackageLoading, 1, entry[1], ": ", message[1] );
628        for i in [ 2 .. Length( message ) ] do
629          Info( InfoPackageLoading, 1, List( entry[1], x -> ' ' ),
630                "  ", message[i] );
631        od;
632      fi;
633    od;
634    end );
635
636
637#############################################################################
638##
639#F  PackageAvailabilityInfo( <name>, <version>, <record>, <suggested>,
640#F      <checkall> )
641##
642InstallGlobalFunction( PackageAvailabilityInfo,
643    function( name, version, record, suggested, checkall )
644    local InvalidStrongDependencies, Name, equal, comp, pair, currversion,
645          inforec, skip, msg, dep, record_local, wght, pos, needed, test,
646          name2, testpair;
647
648    InvalidStrongDependencies:= function( dependencies, weights,
649                                          strong_dependencies )
650      local result, order, pair, cycle;
651
652      result:= false;
653      if not IsEmpty( strong_dependencies ) then
654        order:= LinearOrderByPartialWeakOrder( dependencies, weights ).cycles;
655        for pair in strong_dependencies do
656          for cycle in order do
657            if IsSubset( cycle, pair ) then
658              # This condition was imposed by some
659              # `OtherPackagesLoadedInAdvance' component.
660              LogPackageLoadingMessage( PACKAGE_INFO,
661                  [ Concatenation( "PackageAvailabilityInfo: package '",
662                        pair[1], "'" ),
663                    Concatenation( "shall be loaded before package '", name,
664                        "' but must be" ),
665                    "in the same load cycle, due to other dependencies" ],
666                  Name );
667              result:= true;
668              if not checkall then
669                return result;
670              fi;
671            fi;
672          od;
673        od;
674      fi;
675      return result;
676    end;
677
678    Name:= name;
679    name:= LowercaseString( name );
680    equal:= "";
681    if 0 < Length( version ) and version[1] = '=' then
682      equal:= "equal";
683    fi;
684
685    if name = "gap" then
686      # This case occurs if a package requires a particular GAP version.
687      return CompareVersionNumbers( GAPInfo.Version, version, equal );
688    fi;
689
690    # 1. If the package `name' is already loaded then compare the version
691    #    number of the loaded package with the required one.
692    #    (Note that at most one version of a package can be available.)
693    if IsBound( GAPInfo.PackagesLoaded.( name ) ) then
694      return CompareVersionNumbers( GAPInfo.PackagesLoaded.( name )[2],
695                                    version, equal );
696    fi;
697
698    # 2. If the function was called from `AutoloadPackages'
699    #    and if the package is listed among the packages to be excluded
700    #    from autoload then exit.
701    if IsBound( GAPInfo.ExcludeFromAutoload )
702       and name in GAPInfo.ExcludeFromAutoload then
703      LogPackageLoadingMessage( PACKAGE_DEBUG,
704          "package to be excluded from autoload, return 'false'", Name );
705      return false;
706    fi;
707
708    # 3. Initialize the dependency info.
709    for comp in [ "AlreadyHandled", "Dependencies", "StrongDependencies",
710                  "InstallationPaths", "Weights" ] do
711      if not IsBound( record.( comp ) ) then
712        record.( comp ):= [];
713      fi;
714    od;
715
716    # 4. Deal with the case that `name' is among the packages
717    #    from whose tests the current check for `name' arose.
718    for pair in record.AlreadyHandled do
719      if name = pair[1] then
720        if CompareVersionNumbers( pair[2], version, equal ) then
721          # The availability of the package will be decided on an outer level.
722          return fail;
723        else
724          # The version assumed on an outer level does not fit.
725          return false;
726        fi;
727      fi;
728    od;
729
730    # 5. In recursive calls, regard the current package as handled,
731    #    of course in the version in question.
732    currversion:= [ name ];
733    Add( record.AlreadyHandled, currversion );
734
735    # 6. Get the info records for the package `name',
736    #    and take the first record that satisfies the conditions.
737    #    (Note that they are ordered w.r.t. descending version numbers.)
738    for inforec in PackageInfo( name ) do
739
740      Name:= inforec.PackageName;
741      skip:= false;
742      currversion[2]:= inforec.Version;
743
744      if IsBound( inforec.Dependencies ) then
745        dep:= inforec.Dependencies;
746      else
747        dep:= rec();
748      fi;
749
750      # Test whether this package version fits.
751      msg:= [ Concatenation( "PackageAvailabilityInfo for version ",
752                             inforec.Version ) ];
753      if version <> "" then
754        if not CompareVersionNumbers( inforec.Version, version, equal ) then
755          # The severity of the log message must be less than `PACKAGE_INFO',
756          # since we do not want to see the message when looking for reasons
757          # why the current package cannot be loaded.
758          LogPackageLoadingMessage( PACKAGE_DEBUG,
759              [ Concatenation( "PackageAvailabilityInfo: version ",
760                    inforec.Version, " does not fit" ),
761                Concatenation( "(required: ", version, ")" ) ],
762              inforec.PackageName );
763          if not checkall then
764            continue;
765          fi;
766          skip:= true;
767        else
768          Add( msg, Concatenation( "(required: ", version, ")" ) );
769          LogPackageLoadingMessage( PACKAGE_INFO, msg,
770              inforec.PackageName );
771        fi;
772      else
773        LogPackageLoadingMessage( PACKAGE_INFO, msg,
774            inforec.PackageName );
775      fi;
776
777      # Test whether the required GAP version fits.
778      if IsBound( dep.GAP )
779         and not CompareVersionNumbers( GAPInfo.Version, dep.GAP ) then
780        LogPackageLoadingMessage( PACKAGE_INFO,
781            Concatenation( "PackageAvailabilityInfo: required GAP version (",
782                dep.GAP, ") does not fit",
783            inforec.PackageName ) );
784        if not checkall then
785          continue;
786        fi;
787        skip:= true;
788      fi;
789
790      # Test whether the availability test function fits.
791      GAPInfo.PackageCurrent:= inforec;
792      test:= inforec.AvailabilityTest();
793      Unbind( GAPInfo.PackageCurrent );
794      if test = true then
795        LogPackageLoadingMessage( PACKAGE_DEBUG,
796            Concatenation( "PackageAvailabilityInfo: the AvailabilityTest",
797                " function returned 'true'" ),
798            inforec.PackageName );
799      else
800        LogPackageLoadingMessage( PACKAGE_INFO,
801            Concatenation( "PackageAvailabilityInfo: the AvailabilityTest",
802                " function returned ", String( test ) ),
803            inforec.PackageName );
804        if not checkall then
805          continue;
806        fi;
807        skip:= true;
808      fi;
809
810      # Locate the `init.g' file of the package.
811      if Filename( [ Directory( inforec.InstallationPath ) ], "init.g" )
812           = fail  then
813        LogPackageLoadingMessage( PACKAGE_WARNING,
814            Concatenation( "PackageAvailabilityInfo: cannot locate `",
815              inforec.InstallationPath,
816              "/init.g', please check the installation" ),
817            inforec.PackageName );
818        if not checkall then
819          continue;
820        fi;
821        skip:= true;
822      fi;
823
824      record_local:= StructuralCopy( record );
825
826      # If the GAP library is not yet loaded then assign
827      # weight 0 to all packages that may be loaded before the GAP library,
828      # and weight 1 to those that need the GAP library to be loaded
829      # in advance.
830      # The latter means that either another package or the GAP library
831      # itself is forced to be loaded in advance,
832      # for example because the current package has no `read.g' file.
833      if Filename( [ Directory( inforec.InstallationPath ) ], "read.g" )
834         = fail or
835         ( not IsBound( GAPInfo.LibraryLoaded ) and
836           IsBound( dep.OtherPackagesLoadedInAdvance ) and
837           not IsEmpty( dep.OtherPackagesLoadedInAdvance ) ) then
838        wght:= 1;
839      else
840        wght:= 0;
841      fi;
842      pos:= PositionProperty( record_local.Weights, pair -> pair[1] = name );
843      if pos = fail then
844        Add( record_local.Weights, [ name, wght ] );
845      else
846        record_local.Weights[ pos ][2]:= wght;
847      fi;
848
849      # Check the dependencies of this package version.
850      needed:= [];
851      if IsBound( dep.OtherPackagesLoadedInAdvance ) then
852        Append( record_local.StrongDependencies,
853                List( dep.OtherPackagesLoadedInAdvance,
854                      x -> [ LowercaseString( x[1] ), name ] ) );
855        Append( needed, dep.OtherPackagesLoadedInAdvance );
856      fi;
857      if IsBound( dep.NeededOtherPackages ) then
858        Append( needed, dep.NeededOtherPackages );
859      fi;
860      test:= true;
861      if IsEmpty( needed ) then
862        LogPackageLoadingMessage( PACKAGE_DEBUG,
863            "PackageAvailabilityInfo: no needed packages",
864            inforec.PackageName );
865      else
866        LogPackageLoadingMessage( PACKAGE_DEBUG, Concatenation(
867            [ "PackageAvailabilityInfo: check needed packages" ],
868            List( needed,
869                  pair -> Concatenation( pair[1], " (", pair[2], ")" ) ) ),
870            inforec.PackageName );
871        for pair in needed do
872          name2:= LowercaseString( pair[1] );
873          testpair:= PackageAvailabilityInfo( name2, pair[2], record_local,
874                         suggested, checkall );
875          if testpair = false then
876            # This dependency is not satisfied.
877            test:= false;
878            LogPackageLoadingMessage( PACKAGE_INFO,
879                Concatenation( "PackageAvailabilityInfo: dependency '",
880                    name2, "' is not satisfied" ), inforec.PackageName );
881            if not checkall then
882              # Skip the check of other dependencies.
883              break;
884            fi;
885          elif testpair <> true then
886            # The package `name2' is available but not yet loaded.
887            Add( record_local.Dependencies, [ name2, name ] );
888          fi;
889        od;
890        LogPackageLoadingMessage( PACKAGE_DEBUG,
891            "PackageAvailabilityInfo: check of needed packages done",
892            inforec.PackageName );
893      fi;
894      if test = false then
895        # At least one package needed by this version is not available,
896        if not checkall then
897          continue;
898        fi;
899        skip:= true;
900      fi;
901
902      if InvalidStrongDependencies( record_local.Dependencies,
903             record_local.Weights, record_local.StrongDependencies ) then
904        # This package version cannot be loaded due to conditions
905        # imposed by `OtherPackagesLoadedInAdvance' components.
906        # (Log messages are added inside the function.)
907        if not checkall then
908          continue;
909        fi;
910        skip:= true;
911      fi;
912
913      # All checks for this version have been performed.
914      # Go to the next installed version if some check failed.
915      if skip then
916        continue;
917      fi;
918
919      # The version given by `inforec' will be taken.
920      # Copy the information back to the argument record.
921      record.InstallationPaths:= record_local.InstallationPaths;
922      Add( record.InstallationPaths,
923           [ name, [ inforec.InstallationPath, inforec.Version,
924                     inforec.PackageName, false ] ] );
925      record.Dependencies:= record_local.Dependencies;
926      record.StrongDependencies:= record_local.StrongDependencies;
927      record.AlreadyHandled:= record_local.AlreadyHandled;
928      record.Weights:= record_local.Weights;
929
930      if suggested and IsBound( dep.SuggestedOtherPackages ) then
931        # Collect info about suggested packages and their dependencies.
932        LogPackageLoadingMessage( PACKAGE_DEBUG, Concatenation(
933            [ "PackageAvailabilityInfo: check suggested packages" ],
934            List( dep.SuggestedOtherPackages,
935                  pair -> Concatenation( pair[1], " (", pair[2], ")" ) ) ),
936            inforec.PackageName );
937        for pair in dep.SuggestedOtherPackages do
938          name2:= LowercaseString( pair[1] );
939          # Do not change the information collected up to now
940          # until we are sure that we will really use the suggested package.
941          record_local:= StructuralCopy( record );
942          test:= PackageAvailabilityInfo( name2, pair[2], record_local,
943                     suggested, checkall );
944          if test <> true then
945            Add( record_local.Dependencies, [ name2, name ] );
946            if IsString( test ) then
947              if InvalidStrongDependencies( record_local.Dependencies,
948                     record_local.Weights,
949                     record_local.StrongDependencies ) then
950                test:= false;
951              fi;
952            fi;
953            if test <> false then
954              record.InstallationPaths:= record_local.InstallationPaths;
955              record.Dependencies:= record_local.Dependencies;
956              record.StrongDependencies:= record_local.StrongDependencies;
957              record.AlreadyHandled:= record_local.AlreadyHandled;
958              record.Weights:= record_local.Weights;
959            fi;
960          fi;
961        od;
962        LogPackageLoadingMessage( PACKAGE_DEBUG,
963            "PackageAvailabilityInfo: check of suggested packages done",
964            inforec.PackageName );
965      fi;
966
967      # Print a warning if the package should better be upgraded.
968      if IsBound( GAPInfo.PackagesRestrictions.( name ) ) then
969        GAPInfo.PackagesRestrictions.( name ).OnLoad( inforec );
970      fi;
971#T component name OnLoad:
972#T shouldn't this be done only if the package is actually loaded?
973
974      LogPackageLoadingMessage( PACKAGE_INFO,
975          Concatenation( "PackageAvailabilityInfo: version ",
976                         inforec.Version, " is available" ),
977          inforec.PackageName );
978
979      return inforec.InstallationPath;
980
981    od;
982
983    # No info record satisfies the requirements.
984    if not IsBound( GAPInfo.PackagesInfo.( name ) ) then
985      inforec:= First( GAPInfo.PackagesInfoRefuseLoad,
986                       r -> LowercaseString( r.PackageName ) = name );
987      if inforec <> fail then
988        # Some versions are installed but all were refused.
989        GAPInfo.PackagesRestrictions.( name ).OnLoad( inforec );
990      fi;
991    fi;
992
993    LogPackageLoadingMessage( PACKAGE_INFO,
994        Concatenation( "PackageAvailabilityInfo: ",
995            "no installed version fits" ), Name );
996
997    return false;
998end );
999
1000
1001#############################################################################
1002##
1003#F  TestPackageAvailability( <name>[, <version>][, <checkall>] )
1004##
1005InstallGlobalFunction( TestPackageAvailability, function( arg )
1006    local name, version, checkall, result;
1007
1008    # Get the arguments.
1009    name:= LowercaseString( arg[1] );
1010    version:= "";
1011    checkall:= false;
1012    if Length( arg ) = 2 then
1013      if IsString( arg[2] ) then
1014        version:= arg[2];
1015      elif arg[2] = true then
1016        checkall:= true;
1017      fi;
1018    elif Length( arg ) = 3 then
1019      if IsString( arg[2] ) then
1020        version:= arg[2];
1021      fi;
1022      if arg[3] = true then
1023        checkall:= true;
1024      fi;
1025    fi;
1026
1027    # Ignore suggested packages.
1028    result:= PackageAvailabilityInfo( name, version, rec(), false,
1029                                      checkall );
1030
1031    if result = false then
1032      return fail;
1033    else
1034      return result;
1035    fi;
1036    end );
1037
1038
1039#############################################################################
1040##
1041#F  IsPackageLoaded( <name>[, <version>] )
1042##
1043InstallGlobalFunction( IsPackageLoaded, function( name, version... )
1044    local result;
1045
1046    if Length(version) > 0 then
1047        version := version[1];
1048    fi;
1049    result := IsPackageMarkedForLoading( name, version );
1050    if result then
1051        # check if the package actually completed loading
1052        result := GAPInfo.PackagesLoaded.( name )[4];
1053    fi;
1054    return result;
1055    end );
1056
1057
1058#############################################################################
1059##
1060#F  IsPackageMarkedForLoading( <name>, <version> )
1061##
1062InstallGlobalFunction( IsPackageMarkedForLoading, function( name, version )
1063    local equal;
1064
1065    equal:= "";
1066    if 0 < Length( version ) and version[1] = '=' then
1067      equal:= "equal";
1068    fi;
1069    name:= LowercaseString( name );
1070    return IsBound( GAPInfo.PackagesLoaded.( name ) )
1071           and CompareVersionNumbers( GAPInfo.PackagesLoaded.( name )[2],
1072                   version, equal );
1073    end );
1074
1075
1076#############################################################################
1077##
1078#F  DefaultPackageBannerString( <inforec> )
1079##
1080InstallGlobalFunction( DefaultPackageBannerString, function( inforec )
1081    local len, sep, i, str, authors, maintainers, contributors, printPersons;
1082
1083    # Start with a row of `-' signs.
1084    len:= SizeScreen()[1] - 3;
1085    if GAPInfo.TermEncoding = "UTF-8" then
1086      # The unicode character we use takes up 3 bytes in UTF-8 encoding,
1087      # hence we must adjust the length accordingly.
1088      sep:= "─";
1089      i:= 1;
1090      while 2 * i <= len do
1091        Append( sep, sep );
1092        i:= 2 * i;
1093      od;
1094      Append( sep, sep{ [ 1 .. 3 * ( len - i ) ] } );
1095    else
1096      sep:= ListWithIdenticalEntries( len, '-' );
1097    fi;
1098    Add( sep, '\n' );
1099
1100    str:= "";
1101
1102    # Add package name and version number.
1103    if IsBound( inforec.PackageName ) and IsBound( inforec.Version ) then
1104      Append( str, Concatenation(
1105              "Loading  ", inforec.PackageName, " ", inforec.Version ) );
1106    fi;
1107
1108    # Add the long title.
1109    if IsBound( inforec.PackageDoc ) and IsBound( inforec.PackageDoc[1] ) and
1110       IsBound( inforec.PackageDoc[1].LongTitle ) and
1111       not IsEmpty( inforec.PackageDoc[1].LongTitle ) then
1112      Append( str, Concatenation(
1113              " (", inforec.PackageDoc[1].LongTitle, ")" ) );
1114    fi;
1115    Add( str, '\n' );
1116
1117    # Add info about the authors and/or maintainers
1118    printPersons := function( role, persons )
1119      local fill, person;
1120      fill:= ListWithIdenticalEntries( Length(role), ' ' );
1121      Append( str, role );
1122      for i in [ 1 .. Length( persons ) ] do
1123        person:= persons[i];
1124        Append( str, person.FirstNames );
1125        Append( str, " " );
1126        Append( str, person.LastName );
1127        if   IsBound( person.WWWHome ) then
1128          Append( str, Concatenation( " (", person.WWWHome, ")" ) );
1129        elif IsBound( person.Email ) then
1130          Append( str, Concatenation( " (", person.Email, ")" ) );
1131        fi;
1132        if   i = Length( persons ) then
1133          Append( str, ".\n" );
1134        elif i = Length( persons )-1 then
1135          if i = 1 then
1136            Append( str, " and\n" );
1137          else
1138            Append( str, ", and\n" );
1139          fi;
1140          Append( str, fill );
1141        else
1142          Append( str, ",\n" );
1143          Append( str, fill );
1144        fi;
1145      od;
1146    end;
1147    if IsBound( inforec.Persons ) then
1148      authors:= Filtered( inforec.Persons, x -> x.IsAuthor );
1149      if not IsEmpty( authors ) then
1150        printPersons( "by ", authors );
1151      fi;
1152      contributors:= Filtered( inforec.Persons, x -> not x.IsAuthor and not x.IsMaintainer );
1153      if not IsEmpty( contributors ) then
1154        Append( str, "with contributions by:\n");
1155        printPersons( "   ", contributors );
1156      fi;
1157      maintainers:= Filtered( inforec.Persons, x -> x.IsMaintainer );
1158      if not IsEmpty( maintainers ) and authors <> maintainers then
1159        Append( str, "maintained by:\n");
1160        printPersons( "   ", maintainers );
1161      fi;
1162    fi;
1163
1164    # Add info about the home page of the package.
1165    if IsBound( inforec.PackageWWWHome ) then
1166      Append( str, "Homepage: " );
1167      Append( str, inforec.PackageWWWHome );
1168      Append( str, "\n" );
1169    fi;
1170
1171    # Add info about the issue tracker of the package.
1172    if IsBound( inforec.IssueTrackerURL ) then
1173      Append( str, "Report issues at " );
1174      Append( str, inforec.IssueTrackerURL );
1175      Append( str, "\n" );
1176    fi;
1177
1178    # temporary hack, in some package names with umlauts are in HTML encoding
1179    str := Concatenation(sep, RecodeForCurrentTerminal(str), sep);
1180    str:= ReplacedString( str, "&auml;", RecodeForCurrentTerminal("ä") );
1181    str:= ReplacedString( str, "&ouml;", RecodeForCurrentTerminal("ö") );
1182    str:= ReplacedString( str, "&uuml;", RecodeForCurrentTerminal("ü") );
1183
1184    return str;
1185    end );
1186
1187
1188#############################################################################
1189##
1190#F  DirectoriesPackagePrograms( <name> )
1191##
1192InstallGlobalFunction( DirectoriesPackagePrograms, function( name )
1193    local arch, dirs, info, version, r, path;
1194
1195    arch := GAPInfo.Architecture;
1196    dirs := [];
1197    # We are not allowed to call
1198    # `InstalledPackageVersion', `TestPackageAvailability' etc.
1199    info:= PackageInfo( name );
1200    if IsBound( GAPInfo.PackagesLoaded.( name ) ) then
1201      # The package is already loaded.
1202      version:= GAPInfo.PackagesLoaded.( name )[2];
1203    elif IsBound( GAPInfo.PackageCurrent ) then
1204      # The package is currently going to be loaded.
1205      version:= GAPInfo.PackageCurrent.Version;
1206    elif 0 < Length( info ) then
1207      # Take the installed package with the highest version.
1208      version:= info[1].Version;
1209    fi;
1210    for r in info do
1211      if r.Version = version then
1212        path:= Concatenation( r.InstallationPath, "/bin/", arch, "/" );
1213        Add( dirs, Directory( path ) );
1214      fi;
1215    od;
1216    return dirs;
1217end );
1218
1219
1220#############################################################################
1221##
1222#F  DirectoriesPackageLibrary( <name>[, <path>] )
1223##
1224InstallGlobalFunction( DirectoriesPackageLibrary, function( arg )
1225    local name, path, dirs, info, version, r, tmp;
1226
1227    if IsEmpty(arg) or 2 < Length(arg) then
1228        Error( "usage: DirectoriesPackageLibrary( <name>[, <path>] )\n" );
1229    elif not ForAll(arg, IsString) then
1230        Error( "string argument(s) expected\n" );
1231    fi;
1232
1233    name:= LowercaseString( arg[1] );
1234    if '\\' in name or ':' in name  then
1235        Error( "<name> must not contain '\\' or ':'" );
1236    elif 1 = Length(arg)  then
1237        path := "lib";
1238    else
1239        path := arg[2];
1240    fi;
1241
1242    dirs := [];
1243    # We are not allowed to call
1244    # `InstalledPackageVersion', `TestPackageAvailability' etc.
1245    info:= PackageInfo( name );
1246    if IsBound( GAPInfo.PackagesLoaded.( name ) ) then
1247      # The package is already loaded.
1248      version:= GAPInfo.PackagesLoaded.( name )[2];
1249    elif IsBound( GAPInfo.PackageCurrent ) then
1250      # The package is currently going to be loaded.
1251      version:= GAPInfo.PackageCurrent.Version;
1252    elif 0 < Length( info ) then
1253      # Take the installed package with the highest version.
1254      version:= info[1].Version;
1255    fi;
1256    for r in info do
1257      if r.Version = version then
1258        tmp:= Concatenation( r.InstallationPath, "/", path );
1259        if IsDirectoryPath( tmp ) = true then
1260          Add( dirs, Directory( tmp ) );
1261        fi;
1262      fi;
1263    od;
1264    return dirs;
1265end );
1266
1267
1268#############################################################################
1269##
1270#F  ReadPackage( [<name>, ]<file> )
1271#F  RereadPackage( [<name>, ]<file> )
1272##
1273InstallGlobalFunction( ReadPackage, function( arg )
1274    local pos, relpath, pkgname, namespace, filename, rflc, rfc;
1275
1276    # Note that we cannot use `ReadAndCheckFunc' because this calls
1277    # `READ_GAP_ROOT', but here we have to read the file in one of those
1278    # directories where the package version resides that has been loaded
1279    # or (at least currently) would be loaded.
1280    if   Length( arg ) = 1 then
1281      # Guess the package name.
1282      pos:= Position( arg[1], '/' );
1283      if pos = fail then
1284        ErrorNoReturn(arg[1], " is not a filename in the form 'package/filepath'");
1285      fi;
1286      relpath:= arg[1]{ [ pos+1 .. Length( arg[1] ) ] };
1287      pkgname:= LowercaseString( arg[1]{ [ 1 .. pos-1 ] } );
1288      namespace := GAPInfo.PackagesInfo.(pkgname)[1].PackageName;
1289    elif Length( arg ) = 2 then
1290      pkgname:= LowercaseString( arg[1] );
1291      namespace := GAPInfo.PackagesInfo.(pkgname)[1].PackageName;
1292      relpath:= arg[2];
1293    else
1294      Error( "expected 1 or 2 arguments" );
1295    fi;
1296
1297    # Note that `DirectoriesPackageLibrary' finds the file relative to the
1298    # installation path of the info record chosen in `LoadPackage'.
1299    filename:= Filename( DirectoriesPackageLibrary( pkgname, "" ), relpath );
1300    if filename <> fail and IsReadableFile( filename ) then
1301      ENTER_NAMESPACE(namespace);
1302      Read( filename );
1303      LEAVE_NAMESPACE();
1304      return true;
1305    else
1306      return false;
1307    fi;
1308    end );
1309
1310InstallGlobalFunction( RereadPackage, function( arg )
1311    local res;
1312
1313    MakeReadWriteGlobal( "REREADING" );
1314    REREADING:= true;
1315    MakeReadOnlyGlobal( "REREADING" );
1316    res:= CallFuncList( ReadPackage, arg );
1317    MakeReadWriteGlobal( "REREADING" );
1318    REREADING:= false;
1319    MakeReadOnlyGlobal( "REREADING" );
1320    return res;
1321    end );
1322
1323
1324#############################################################################
1325##
1326#F  LoadPackageDocumentation( <info> )
1327##
1328##  In versions before 4.5, a second argument was required.
1329##  For the sake of backwards compatibility, we do not forbid a second
1330##  argument, but we ignore it.
1331##  (In later versions, we may forbid the second argument.)
1332##
1333InstallGlobalFunction( LoadPackageDocumentation, function( arg )
1334    local info, short, pkgdoc, long, sixfile;
1335
1336    info:= arg[1];
1337
1338    # Load all books for the package.
1339    for pkgdoc in info.PackageDoc do
1340      # Fetch the names.
1341      if IsBound( pkgdoc.LongTitle ) then
1342        long:= pkgdoc.LongTitle;
1343      else
1344        long:= Concatenation( "GAP Package `", info.PackageName, "'" );
1345      fi;
1346      short:= pkgdoc.BookName;
1347      if not IsBound( GAPInfo.PackagesLoaded.( LowercaseString(
1348                          info.PackageName ) ) ) then
1349        short:= Concatenation( short, " (not loaded)" );
1350      fi;
1351
1352      # Check that the `manual.six' file is available.
1353      sixfile:= Filename( [ Directory( info.InstallationPath ) ],
1354                          pkgdoc.SixFile );
1355      if sixfile = fail then
1356        LogPackageLoadingMessage( PACKAGE_INFO,
1357            Concatenation( [ "book `", pkgdoc.BookName,
1358                "': no manual index file `",
1359                pkgdoc.SixFile, "', ignored" ] ),
1360            info.PackageName );
1361      else
1362        # Finally notify the book via its directory.
1363#T Here we assume that this is the directory that contains also `manual.six'!
1364        HELP_ADD_BOOK( short, long,
1365            Directory( sixfile{ [ 1 .. Length( sixfile )-10 ] } ) );
1366      fi;
1367    od;
1368    end );
1369
1370#############################################################################
1371##
1372#F  LoadPackage_ReadImplementationParts( <secondrun>, <banner> )
1373##
1374BindGlobal( "LoadPackage_ReadImplementationParts",
1375    function( secondrun, banner )
1376    local pair, info, bannerstring, fun, u, pkgname, namespace;
1377
1378    for pair in secondrun do
1379      namespace := pair[1].PackageName;
1380      pkgname := LowercaseString( namespace );
1381      if pair[2] <> fail then
1382        GAPInfo.PackageCurrent:= pair[1];
1383        LogPackageLoadingMessage( PACKAGE_DEBUG,
1384            "start reading file 'read.g'",
1385            namespace );
1386        ENTER_NAMESPACE(namespace);
1387        Read( pair[2] );
1388        LEAVE_NAMESPACE();
1389        Unbind( GAPInfo.PackageCurrent );
1390        LogPackageLoadingMessage( PACKAGE_DEBUG,
1391            "finish reading file 'read.g'",
1392            namespace );
1393      fi;
1394      # mark the package as completely loaded
1395      GAPInfo.PackagesLoaded.(pkgname)[4] := true;
1396      MakeImmutable( GAPInfo.PackagesLoaded.(pkgname) );
1397    od;
1398
1399    # Show the banners.
1400    if banner then
1401      for pair in secondrun do
1402        info:= pair[1];
1403
1404        # If the component `BannerString' is bound in `info' then we print
1405        # this string, otherwise we print the default banner string.
1406        if IsBound( info.BannerFunction ) then
1407          bannerstring:= RecodeForCurrentTerminal(info.BannerFunction(info));
1408        elif IsBound( info.BannerString ) then
1409          bannerstring:= RecodeForCurrentTerminal(info.BannerString);
1410        else
1411          bannerstring:= DefaultPackageBannerString( info );
1412        fi;
1413
1414        # Be aware of umlauts, accents etc. in the banner.
1415        if IsBoundGlobal( "Unicode" ) and IsBoundGlobal( "Encode" ) then
1416          # The GAPDoc package is completely loaded.
1417          fun:= ValueGlobal( "PrintFormattedString" );
1418          fun( bannerstring );
1419        else
1420          # GAPDoc is not available, simply print the banner string as is.
1421          Print( bannerstring );
1422        fi;
1423      od;
1424    fi;
1425    end );
1426
1427
1428#############################################################################
1429##
1430#F  GetPackageNameForPrefix( <prefix> ) . . . . . . . .  show list of matches
1431#F                                                   or single match directly
1432##
1433##  Compute all names of installed packages that match the prefix <prefix>.
1434##  In case of a unique match return this match,
1435##  otherwise print an info message about the matches and return <prefix>.
1436##
1437##  This function is called by `LoadPackage'.
1438##
1439BindGlobal( "GetPackageNameForPrefix", function( prefix )
1440    local len, lowernames, name, allnames, indent, pos, sep;
1441
1442    len:= Length( prefix );
1443    lowernames:= [];
1444    for name in Set( RecNames( GAPInfo.PackagesInfo ) ) do
1445      if Length( prefix ) <= Length( name ) and
1446         name{ [ 1 .. len ] } = prefix then
1447        Add( lowernames, name );
1448      fi;
1449    od;
1450    if IsEmpty( lowernames ) then
1451      # No package name matches.
1452      return prefix;
1453    fi;
1454    allnames:= List( lowernames,
1455                     nam -> GAPInfo.PackagesInfo.( nam )[1].PackageName );
1456    if Length( allnames ) = 1 then
1457      # There is one exact match.
1458      LogPackageLoadingMessage( PACKAGE_DEBUG, Concatenation(
1459          [ "replace prefix '", prefix, "' by the unique completion '",
1460            allnames[1], "'" ] ), allnames[1] );
1461      return lowernames[1];
1462    fi;
1463
1464    # Several package names match.
1465    if 0 < InfoLevel( InfoPackageLoading ) then
1466      Print( "#I  Call 'LoadPackage' with one of the following strings:\n" );
1467      len:= SizeScreen()[1] - 6;
1468      indent:= "#I  ";
1469      Print( indent );
1470      pos:= Length( indent );
1471      sep:= "";
1472      for name in allnames do
1473        Print( sep );
1474        pos:= pos + Length( sep );
1475        if len < pos + Length( name ) then
1476          Print( "\n", indent );
1477          pos:= Length( indent );
1478        fi;
1479        Print( "\"", name, "\"" );
1480        pos:= pos + Length( name ) + 2;
1481        sep:= ", ";
1482      od;
1483      Print( ".\n" );
1484    fi;
1485    return prefix;
1486    end );
1487
1488
1489#############################################################################
1490##
1491#F  LoadPackage( <name>[, <version>][, <banner>] )
1492##
1493InstallGlobalFunction( LoadPackage, function( arg )
1494    local name, Name, version, banner, loadsuggested, msg, depinfo, path,
1495          pair, i, order, paths, cycle, secondrun, pkgname, pos, info,
1496          filename, read;
1497
1498    # Get the arguments.
1499    if Length( arg ) = 0 then
1500      name:= "";
1501    else
1502      name:= arg[1];
1503      if not IsString( name ) then
1504        Error( "<name> must be a string" );
1505      fi;
1506      name:= LowercaseString( name );
1507    fi;
1508    if not IsBound( GAPInfo.PackagesInfo.( name ) ) then
1509      name:= GetPackageNameForPrefix( name );
1510    fi;
1511
1512    # Return 'fail' if this package is not installed.
1513    if not IsBound( GAPInfo.PackagesInfo.( name ) ) then
1514      LogPackageLoadingMessage( PACKAGE_DEBUG,
1515          "no package with this name is installed, return 'fail'", name );
1516      if InfoLevel(InfoPackageLoading) < 4 then
1517        Info(InfoWarning,1, name, " package is not available. Check that the name is correct");
1518        Info(InfoWarning,1, "and it is present in one of the GAP root directories (see '??RootPaths')");
1519      fi;
1520      return fail;
1521    fi;
1522
1523    # The package is available, fetch the name for messages.
1524    Name:= GAPInfo.PackagesInfo.( name )[1].PackageName;
1525    version:= "";
1526    banner:= not GAPInfo.CommandLineOptions.q and
1527             not GAPInfo.CommandLineOptions.b;
1528    if 1 < Length( arg ) then
1529      if IsString( arg[2] ) then
1530        version:= arg[2];
1531        if 2 < Length( arg ) then
1532          banner:= banner and not ( arg[3] = false );
1533        fi;
1534      else
1535        banner:= banner and not ( arg[2] = false );
1536      fi;
1537    fi;
1538    loadsuggested:= ( ValueOption( "OnlyNeeded" ) <> true );
1539
1540    # Print a warning if `LoadPackage' is called inside a
1541    # `LoadPackage' call.
1542    if not IsBound( GAPInfo.LoadPackageLevel ) then
1543      GAPInfo.LoadPackageLevel:= 0;
1544    fi;
1545    GAPInfo.LoadPackageLevel:= GAPInfo.LoadPackageLevel + 1;
1546    if GAPInfo.LoadPackageLevel <> 1 then
1547      if IsBound( GAPInfo.PackageCurrent ) then
1548        msg:= GAPInfo.PackageCurrent.PackageName;
1549      else
1550        msg:= "?";
1551      fi;
1552      LogPackageLoadingMessage( PACKAGE_WARNING,
1553          [ Concatenation( "Do not call `LoadPackage( \"", name,
1554                "\", ... )' in the package file" ),
1555            Concatenation( INPUT_FILENAME(), "," ),
1556            "use `IsPackageMarkedForLoading' instead" ], msg );
1557    fi;
1558
1559    # Start logging.
1560    msg:= "entering LoadPackage ";
1561    if not loadsuggested then
1562      Append( msg, " (omitting suggested packages)" );
1563    fi;
1564    LogPackageLoadingMessage( PACKAGE_DEBUG, msg, Name );
1565
1566    # Test whether the package is available,
1567    # and compute the dependency information.
1568    depinfo:= rec();
1569    path:= PackageAvailabilityInfo( name, version, depinfo, loadsuggested,
1570                                    false );
1571    if not IsString( path ) then
1572      if path = false then
1573        path:= fail;
1574      fi;
1575      # The result is either `true' (the package is already loaded)
1576      # or `fail' (the package cannot be loaded).
1577      if path = true then
1578        LogPackageLoadingMessage( PACKAGE_DEBUG,
1579            "return from LoadPackage, package was already loaded", Name );
1580      else
1581        LogPackageLoadingMessage( PACKAGE_DEBUG,
1582            "return from LoadPackage, package is not available", Name );
1583        if banner then
1584          if InfoLevel(InfoPackageLoading) < 4 then
1585            Info(InfoWarning,1, Name, " package is not available. To see further details, enter");
1586            Info(InfoWarning,1, "SetInfoLevel(InfoPackageLoading,4); and try to load the package again.");
1587          fi;
1588        fi;
1589      fi;
1590      GAPInfo.LoadPackageLevel:= GAPInfo.LoadPackageLevel - 1;
1591      return path;
1592    fi;
1593
1594    # Suspend reordering of methods following InstallTrueMethod
1595    # because it would slow things down too much
1596    SuspendMethodReordering();
1597
1598    # Compute the order in which the packages are loaded.
1599    # For each set of packages with cyclic dependencies,
1600    # we will first read all `init.g' files
1601    # and afterwards all `read.g' files.
1602    if IsEmpty( depinfo.Dependencies ) then
1603      order:= rec( cycles:= [ [ name ] ],
1604                   weights:= [ depinfo.Weights[1][2] ] );
1605    else
1606      order:= LinearOrderByPartialWeakOrder( depinfo.Dependencies,
1607                                             depinfo.Weights );
1608    fi;
1609    # paths:= TransposedMatMutable( depinfo.InstallationPaths );
1610    # (TransposedMatMutable is not yet available here ...)
1611    paths:= [ [], [] ];
1612    for pair in depinfo.InstallationPaths do
1613      Add( paths[1], pair[1] );
1614      Add( paths[2], pair[2] );
1615    od;
1616    SortParallel( paths[1], paths[2] );
1617
1618    secondrun:= [];
1619    for i in [ 1 .. Length( order.cycles ) ] do
1620      cycle:= order.cycles[i];
1621
1622      # First mark all packages in the current cycle as loaded,
1623      # in order to avoid that an occasional call of `LoadPackage'
1624      # inside the package code causes the files to be read more than once.
1625      for pkgname in cycle do
1626        pos:= PositionSorted( paths[1], pkgname );
1627        # the following entry is made immutable in LoadPackage_ReadImplementationParts
1628        GAPInfo.PackagesLoaded.( pkgname ):= paths[2][ pos ];
1629      od;
1630
1631      # If the weight is 1 and the GAP library is not yet loaded
1632      # then load the GAP library now.
1633      if order.weights[i] = 1 and not IsBound( GAPInfo.LibraryLoaded ) then
1634        LogPackageLoadingMessage( PACKAGE_DEBUG,
1635            [ "read the impl. part of the GAP library" ], Name );
1636        ReadGapRoot( "lib/read.g" );
1637        GAPInfo.LibraryLoaded:= true;
1638        LoadPackage_ReadImplementationParts( Concatenation(
1639            GAPInfo.delayedImplementationParts, secondrun ), false );
1640        GAPInfo.delayedImplementationParts:= [];
1641        secondrun:= [];
1642      fi;
1643
1644      if loadsuggested then
1645        msg:= "start loading needed/suggested/self packages";
1646      else
1647        msg:= "start loading needed/self packages";
1648      fi;
1649      LogPackageLoadingMessage( PACKAGE_DEBUG,
1650          Concatenation( [ msg ], cycle ),
1651          Name );
1652
1653      for pkgname in cycle do
1654        pos:= PositionSorted( paths[1], pkgname );
1655        info:= First( PackageInfo( pkgname ),
1656                      r -> r.InstallationPath = paths[2][ pos ][1] );
1657
1658        if not ValidatePackageInfo(info) then
1659           Print("#E Validation of package ", pkgname, " from ", info.InstallationPath, " failed\n");
1660        fi;
1661
1662        # Notify the documentation (for the available version).
1663        LoadPackageDocumentation( info );
1664
1665        # Read the `init.g' files.
1666        LogPackageLoadingMessage( PACKAGE_DEBUG,
1667            "start reading file 'init.g'",
1668            info.PackageName );
1669        GAPInfo.PackageCurrent:= info;
1670        ReadPackage( pkgname, "init.g" );
1671        Unbind( GAPInfo.PackageCurrent );
1672        LogPackageLoadingMessage( PACKAGE_DEBUG,
1673            "finish reading file 'init.g'",
1674            info.PackageName );
1675
1676        filename:= Filename( [ Directory( info.InstallationPath ) ],
1677                             "read.g" );
1678        Add( secondrun, [ info, filename ] );
1679      od;
1680
1681      if IsBound( GAPInfo.LibraryLoaded )
1682         and GAPInfo.LibraryLoaded = true then
1683        # Read the `read.g' files collected up to now.
1684        # Afterwards show the banners.
1685        # (We have delayed this until now because it uses functionality
1686        # from the package GAPDoc.)
1687        # Note that no banners are printed during autoloading.
1688        LoadPackage_ReadImplementationParts( secondrun, banner );
1689        secondrun:= [];
1690      fi;
1691
1692    od;
1693
1694    if not IsBound( GAPInfo.LibraryLoaded ) then
1695      Append( GAPInfo.delayedImplementationParts, secondrun );
1696    fi;
1697
1698    LogPackageLoadingMessage( PACKAGE_DEBUG, "return from LoadPackage",
1699        Name );
1700    GAPInfo.LoadPackageLevel:= GAPInfo.LoadPackageLevel - 1;
1701
1702    ResumeMethodReordering();
1703    return true;
1704    end );
1705
1706
1707#############################################################################
1708##
1709#F  LoadAllPackages()
1710##
1711InstallGlobalFunction( LoadAllPackages, function()
1712    SuspendMethodReordering();
1713    if ValueOption( "reversed" ) = true then
1714        List( Reversed( RecNames( GAPInfo.PackagesInfo ) ), LoadPackage );
1715    else
1716        List( RecNames( GAPInfo.PackagesInfo ), LoadPackage );
1717    fi;
1718    ResumeMethodReordering();
1719    end );
1720
1721
1722#############################################################################
1723##
1724#F  SetPackagePath( <pkgname>, <pkgpath> )
1725##
1726InstallGlobalFunction( SetPackagePath, function( pkgname, pkgpath )
1727    local pkgdir, file, record, version;
1728
1729    InitializePackagesInfoRecords();
1730    pkgname:= LowercaseString( pkgname );
1731    NormalizeWhitespace( pkgname );
1732    if IsBound( GAPInfo.PackagesLoaded.( pkgname ) ) then
1733      if GAPInfo.PackagesLoaded.( pkgname )[1] = pkgpath then
1734        return;
1735      fi;
1736      Error( "another version of package ", pkgname, " is already loaded" );
1737    fi;
1738
1739    pkgdir:= Directory( pkgpath );
1740    file:= Filename( [ pkgdir ], "PackageInfo.g" );
1741    if file = fail then
1742      file:= Filename( [ pkgdir ], "PkgInfo.g" );
1743    fi;
1744    if file = fail then
1745      return;
1746    fi;
1747    Unbind( GAPInfo.PackageInfoCurrent );
1748    Read( file );
1749    record:= GAPInfo.PackageInfoCurrent;
1750    Unbind( GAPInfo.PackageInfoCurrent );
1751    if IsBound( record.PkgName ) then
1752      record.PackageName:= record.PkgName;
1753    fi;
1754    if pkgname <> NormalizedWhitespace( LowercaseString(
1755                      record.PackageName ) ) then
1756      Error( "found package ", record.PackageName, " not ", pkgname,
1757             " in ", pkgpath );
1758    fi;
1759    version:= record.Version;
1760    if IsBound( GAPInfo.PackagesRestrictions.( pkgname ) )
1761       and GAPInfo.PackagesRestrictions.( pkgname ).OnInitialization(
1762               record ) = false  then
1763      Add( GAPInfo.PackagesInfoRefuseLoad, record );
1764    else
1765      record.InstallationPath:= Filename( [ pkgdir ], "" );
1766      if not IsBound( record.PackageDoc ) then
1767        record.PackageDoc:= [];
1768      elif IsRecord( record.PackageDoc ) then
1769        record.PackageDoc:= [ record.PackageDoc ];
1770      fi;
1771    fi;
1772    GAPInfo.PackagesInfo.( pkgname ):= [ record ];
1773    end );
1774
1775
1776#############################################################################
1777##
1778#F  ExtendRootDirectories( <paths> )
1779##
1780InstallGlobalFunction( ExtendRootDirectories, function( rootpaths )
1781    rootpaths:= Filtered( rootpaths, path -> not path in GAPInfo.RootPaths );
1782    if not IsEmpty( rootpaths ) then
1783      # Append the new root paths.
1784      GAPInfo.RootPaths:= Immutable( Concatenation( GAPInfo.RootPaths,
1785          rootpaths ) );
1786      # Clear the cache.
1787      GAPInfo.DirectoriesLibrary:= AtomicRecord( rec() );
1788      # Reread the package information.
1789      if IsBound( GAPInfo.PackagesInfoInitialized ) and
1790         GAPInfo.PackagesInfoInitialized = true then
1791        GAPInfo.PackagesInfoInitialized:= false;
1792        InitializePackagesInfoRecords();
1793      fi;
1794    fi;
1795    end );
1796
1797
1798#############################################################################
1799##
1800#F  InstalledPackageVersion( <name> )
1801##
1802InstallGlobalFunction( InstalledPackageVersion, function( name )
1803    local avail, info;
1804
1805    avail:= TestPackageAvailability( name, "" );
1806    if   avail = fail then
1807      return fail;
1808    elif avail = true then
1809      return GAPInfo.PackagesLoaded.( LowercaseString( name ) )[2];
1810    fi;
1811    info:= First( PackageInfo( name ), r -> r.InstallationPath = avail );
1812    return info.Version;
1813    end );
1814
1815
1816#############################################################################
1817##
1818#F  AutoloadPackages()
1819##
1820
1821# The packages to load during startup can be specified via a user preference.
1822DeclareUserPreference( rec(
1823  name:= "PackagesToLoad",
1824  description:= [
1825    "A list of names of packages which should be loaded during startup. \
1826For backwards compatibility, the default lists most of packages \
1827that were autoloaded in GAP 4.4 (add or remove packages as you like)."
1828    ],
1829  default:= [ "autpgrp", "alnuth", "crisp", "ctbllib", "factint", "fga",
1830              "irredsol", "laguna", "polenta", "polycyclic", "resclasses",
1831              "sophus", "tomlib" ],
1832  values:= function() return RecNames( GAPInfo.PackagesInfo ); end,
1833  multi:= true,
1834  ) );
1835
1836# And a preference for avoiding some packages.
1837DeclareUserPreference( rec(
1838  name:= "ExcludeFromAutoload",
1839  description:= [
1840    "These packages are not loaded at GAP startup. This doesn't work for \
1841packages which are needed by the GAP library, or which are already loaded \
1842in a workspace."
1843    ],
1844  default:= [],
1845  values:= function() return RecNames( GAPInfo.PackagesInfo ); end,
1846  multi:= true,
1847  ) );
1848
1849# And a preference for ignoring some packages completely during the session.
1850DeclareUserPreference( rec(
1851  name:= "PackagesToIgnore",
1852  description:= [
1853    "These packages are not regarded as available. This doesn't work for \
1854packages which are needed by the GAP library, or which are already loaded \
1855in a workspace."
1856    ],
1857  default:= [],
1858  values:= function() return RecNames( GAPInfo.PackagesInfo ); end,
1859  multi:= true,
1860  ) );
1861
1862# And a preference for setting the info level of package loading.
1863DeclareUserPreference( rec(
1864  name:= "InfoPackageLoadingLevel",
1865  description:= [
1866    "Info messages concerning package loading up to this level are printed.  \
1867The level can be changed in a running session using 'SetInfoLevel'."
1868    ],
1869  default:= PACKAGE_ERROR,
1870  values:= [ PACKAGE_ERROR, PACKAGE_WARNING, PACKAGE_INFO, PACKAGE_DEBUG ],
1871  multi:= false,
1872  ) );
1873
1874InstallGlobalFunction( AutoloadPackages, function()
1875    local banner, msg, pair, excludedpackages, name, record;
1876
1877#T remove this as soon as `BANNER' is not used anymore in packages
1878if IsBoundGlobal( "BANNER" ) then
1879  banner:= ValueGlobal( "BANNER" );
1880  MakeReadWriteGlobal( "BANNER" );
1881  UnbindGlobal( "BANNER" );
1882fi;
1883BindGlobal( "BANNER", false );
1884
1885    if GAPInfo.CommandLineOptions.L = "" then
1886      msg:= "entering AutoloadPackages (no workspace)";
1887    else
1888      msg:= Concatenation( "entering AutoloadPackages (workspace ",
1889                           GAPInfo.CommandLineOptions.L, ")" ) ;
1890    fi;
1891    LogPackageLoadingMessage( PACKAGE_DEBUG, msg, "GAP" );
1892
1893    SetInfoLevel( InfoPackageLoading,
1894        UserPreference( "InfoPackageLoadingLevel" ) );
1895
1896    GAPInfo.ExcludeFromAutoload:= [];
1897    GAPInfo.PackagesInfoInitialized:= false;
1898    InitializePackagesInfoRecords();
1899
1900    GAPInfo.delayedImplementationParts:= [];
1901
1902    # Load the needed other packages (suppressing banners)
1903    # that are not yet loaded.
1904    if ForAny( GAPInfo.Dependencies.NeededOtherPackages,
1905               p -> not IsBound( GAPInfo.PackagesLoaded.( p[1] ) ) ) then
1906      LogPackageLoadingMessage( PACKAGE_DEBUG,
1907          Concatenation( [ "trying to load needed packages" ],
1908              List( GAPInfo.Dependencies.NeededOtherPackages,
1909                  pair -> Concatenation( pair[1], " (", pair[2], ")" ) ) ),
1910          "GAP" );
1911      if GAPInfo.CommandLineOptions.A then
1912        PushOptions( rec( OnlyNeeded:= true ) );
1913      fi;
1914      for pair in GAPInfo.Dependencies.NeededOtherPackages do
1915        if LoadPackage( pair[1], pair[2], false ) <> true then
1916          LogPackageLoadingMessage( PACKAGE_ERROR, Concatenation(
1917              "needed package ", pair[1], " cannot be loaded" ), "GAP" );
1918          Error( "failed to load needed package `", pair[1],
1919                 "' (version ", pair[2], ")" );
1920        fi;
1921      od;
1922      LogPackageLoadingMessage( PACKAGE_DEBUG, "needed packages loaded",
1923          "GAP" );
1924      if GAPInfo.CommandLineOptions.A then
1925        PopOptions();
1926      fi;
1927    fi;
1928
1929    # If necessary then load the implementation part of the GAP library,
1930    # and the implementation parts of the packages loaded up to now.
1931    if not IsBound( GAPInfo.LibraryLoaded ) then
1932      LogPackageLoadingMessage( PACKAGE_DEBUG,
1933          [ "read the impl. part of the GAP library" ], "GAP" );
1934      ReadGapRoot( "lib/read.g" );
1935      GAPInfo.LibraryLoaded:= true;
1936      GAPInfo.LoadPackageLevel:= GAPInfo.LoadPackageLevel + 1;
1937      LoadPackage_ReadImplementationParts(
1938          GAPInfo.delayedImplementationParts, false );
1939      GAPInfo.LoadPackageLevel:= GAPInfo.LoadPackageLevel - 1;
1940    fi;
1941    Unbind( GAPInfo.delayedImplementationParts );
1942#T remove this as soon as `BANNER' is not used anymore in packages
1943MakeReadWriteGlobal( "BANNER" );
1944UnbindGlobal( "BANNER" );
1945if IsBound( banner ) then
1946  BindGlobal( "BANNER", banner );
1947fi;
1948
1949    # Load suggested packages of GAP (suppressing banners).
1950    if   GAPInfo.CommandLineOptions.A then
1951      LogPackageLoadingMessage( PACKAGE_DEBUG,
1952          "omitting packages suggested via \"PackagesToLoad\" (-A option)",
1953          "GAP" );
1954    elif ValueOption( "OnlyNeeded" ) = true then
1955      LogPackageLoadingMessage( PACKAGE_DEBUG,
1956          [ "omitting packages suggested via \"PackagesToLoad\"",
1957            " ('OnlyNeeded' option)" ],
1958          "GAP" );
1959    elif ForAny( List( UserPreference( "PackagesToLoad" ), LowercaseString ),
1960                 p -> not IsBound( GAPInfo.PackagesLoaded.( p ) ) ) then
1961
1962      # Try to load the suggested other packages (suppressing banners),
1963      # issue a warning for each such package where this is not possible.
1964      excludedpackages:= List( UserPreference( "ExcludeFromAutoload" ),
1965                               LowercaseString );
1966      LogPackageLoadingMessage( PACKAGE_DEBUG,
1967          Concatenation( [ "trying to load suggested packages" ],
1968              UserPreference( "PackagesToLoad" ) ),
1969          "GAP" );
1970      for name in UserPreference( "PackagesToLoad" ) do
1971#T admit pair [ name, version ] in user preferences!
1972        if LowercaseString( name ) in excludedpackages then
1973          LogPackageLoadingMessage( PACKAGE_DEBUG,
1974              Concatenation( "excluded from autoloading: ", name ),
1975              "GAP" );
1976        elif not IsBound( GAPInfo.PackagesLoaded.( LowercaseString( name ) ) ) then
1977          LogPackageLoadingMessage( PACKAGE_DEBUG,
1978              Concatenation( "considering for autoloading: ", name ),
1979              "GAP" );
1980          if LoadPackage( name, false ) <> true then
1981            LogPackageLoadingMessage( PACKAGE_DEBUG,
1982                 Concatenation( "suggested package ", name,
1983                     " cannot be loaded" ), "GAP" );
1984          else
1985            LogPackageLoadingMessage( PACKAGE_DEBUG,
1986                Concatenation( name, " loaded" ), "GAP" );
1987#T possible to get the right case of the name?
1988          fi;
1989        fi;
1990      od;
1991      LogPackageLoadingMessage( PACKAGE_DEBUG,
1992          "suggested packages loaded", "GAP" );
1993    fi;
1994
1995    # Load the documentation for not yet loaded packages.
1996    LogPackageLoadingMessage( PACKAGE_DEBUG,
1997        "call LoadPackageDocumentation for not loaded packages",
1998        "GAP" );
1999    for name in RecNames( GAPInfo.PackagesInfo ) do
2000      if not IsBound( GAPInfo.PackagesLoaded.( name ) ) then
2001        # Note that the info records for each package are sorted
2002        # w.r.t. decreasing version number.
2003        record:= First( GAPInfo.PackagesInfo.( name ), IsRecord );
2004        if record <> fail then
2005          LoadPackageDocumentation( record );
2006        fi;
2007      fi;
2008    od;
2009    LogPackageLoadingMessage( PACKAGE_DEBUG,
2010        "LoadPackageDocumentation for not loaded packages done",
2011        "GAP" );
2012
2013    Unbind( GAPInfo.ExcludeFromAutoload );
2014
2015    LogPackageLoadingMessage( PACKAGE_DEBUG,
2016        "return from AutoloadPackages",
2017        "GAP" );
2018    end );
2019
2020
2021#############################################################################
2022##
2023#F  GAPDocManualLab(<pkgname>) . create manual.lab for package w/ GAPDoc docs
2024##
2025# avoid warning (will be def. in GAPDoc)
2026if not IsBound(StripEscapeSequences) then
2027  StripEscapeSequences := 0;
2028fi;
2029InstallGlobalFunction( GAPDocManualLabFromSixFile,
2030    function( bookname, sixfilepath )
2031    local stream, entries, SecNumber, esctex, file;
2032
2033    stream:= InputTextFile( sixfilepath );
2034
2035    atomic readonly HELP_REGION do
2036      entries:= HELP_BOOK_HANDLER.GapDocGAP.ReadSix( stream ).entries;
2037    od;
2038
2039    SecNumber:= function( list )
2040      if IsEmpty( list ) or list[1] = 0 then
2041        return "";
2042      fi;
2043      while list[ Length( list ) ] = 0 do
2044        Unbind( list[ Length( list ) ] );
2045      od;
2046      return JoinStringsWithSeparator( List( list, String ), "." );
2047    end;
2048
2049    # throw away TeX critical characters here
2050    esctex:= function( str )
2051      return Filtered( StripEscapeSequences( str ), c -> not c in "%#$&^_~" );
2052    end;
2053
2054    bookname:= LowercaseString( bookname );
2055    entries:= List( entries,
2056                     entry -> Concatenation( "\\makelabel{", bookname, ":",
2057                                             esctex(entry[1]), "}{",
2058                                             SecNumber( entry[3] ), "}{",
2059                                             entry[7], "}\n" ) );
2060    # forget entries that contain a character from "\\*+/=" in label,
2061    # these were never allowed, so no old manual will refer to them
2062    entries := Filtered(entries, entry ->
2063                    not ForAny("\\*+/=", c-> c in entry{[9..Length(entry)]}));
2064    file:= Concatenation( sixfilepath{ [ 1 .. Length( sixfilepath ) - 3 ] },
2065                          "lab" );
2066    # add marker line
2067    entries := Concatenation (
2068        [Concatenation ("\\GAPDocLabFile{", bookname,"}\n")],
2069        entries);
2070    FileString( file, Concatenation( entries ) );
2071    Info( InfoWarning, 1, "File: ", file, " written." );
2072end );
2073
2074InstallGlobalFunction( GAPDocManualLab, function(pkgname)
2075  local pinf, book, file;
2076
2077  if not IsString(pkgname) then
2078    Error("argument <pkgname> should be a string\n");
2079  fi;
2080  pkgname := LowercaseString(pkgname);
2081  LoadPackage(pkgname);
2082  if not IsBound(GAPInfo.PackagesInfo.(pkgname)) then
2083    Error("Could not load package ", pkgname, ".\n");
2084  fi;
2085  if LoadPackage("GAPDoc") <> true then
2086    Error("package `GAPDoc' not installed. Please install `GAPDoc'\n" );
2087  fi;
2088
2089  pinf := GAPInfo.PackagesInfo.(pkgname)[1];
2090  for book in pinf.PackageDoc do
2091    file := Filename([Directory(pinf.InstallationPath)], book.SixFile);
2092    if file = fail or not IsReadableFile(file) then
2093      Error("could not open `manual.six' file of package `", pkgname, "'.\n",
2094            "Please compile its documentation\n");
2095    fi;
2096    GAPDocManualLabFromSixFile( book.BookName, file );
2097  od;
2098end );
2099if StripEscapeSequences = 0 then
2100  Unbind(StripEscapeSequences);
2101fi;
2102
2103
2104#############################################################################
2105##
2106#F  DeclareAutoreadableVariables( <pkgname>, <filename>, <varlist> )
2107##
2108InstallGlobalFunction( DeclareAutoreadableVariables,
2109    function( pkgname, filename, varlist )
2110    CallFuncList( AUTO, Concatenation( [
2111      function( x )
2112        # Avoid nested calls to `RereadPackage',
2113        # which could cause that `REREADING' is set to `false' too early.
2114        if REREADING then
2115          ReadPackage( pkgname, filename );
2116        else
2117          RereadPackage( pkgname, filename );
2118        fi;
2119      end, filename ], varlist ) );
2120    end );
2121
2122
2123#############################################################################
2124##
2125##  Tests whether loading a package works and does not obviously break
2126##  anything.
2127##  (This is very preliminary.)
2128##
2129
2130
2131#############################################################################
2132##
2133#F  ValidatePackageInfo( <info> )
2134##
2135InstallGlobalFunction( ValidatePackageInfo, function( info )
2136    local record, pkgdir, i, IsStringList, IsRecordList, IsProperBool, IsURL,
2137          IsFilename, IsFilenameList, result, TestOption, TestMandat, subrec,
2138          list;
2139
2140    if IsString( info ) then
2141      if IsReadableFile( info ) then
2142        Unbind( GAPInfo.PackageInfoCurrent );
2143        Read( info );
2144        if IsBound( GAPInfo.PackageInfoCurrent ) then
2145          record:= GAPInfo.PackageInfoCurrent;
2146          Unbind( GAPInfo.PackageInfoCurrent );
2147        else
2148          Error( "the file <info> is not a `PackageInfo.g' file" );
2149        fi;
2150        pkgdir:= "./";
2151        for i in Reversed( [ 1 .. Length( info ) ] ) do
2152          if info[i] = '/' then
2153            pkgdir:= info{ [ 1 .. i ] };
2154            break;
2155          fi;
2156        od;
2157      else
2158        Error( "<info> is not the name of a readable file" );
2159      fi;
2160    elif IsRecord( info ) then
2161      pkgdir:= fail;
2162      record:= info;
2163    else
2164      Error( "<info> must be either a record or a filename" );
2165    fi;
2166
2167    IsStringList:= x -> IsList( x ) and ForAll( x, IsString );
2168    IsRecordList:= x -> IsList( x ) and ForAll( x, IsRecord );
2169    IsProperBool:= x -> x = true or x = false;
2170    IsFilename:= x -> IsString( x ) and Length( x ) > 0 and
2171        ( pkgdir = fail or
2172          ( x[1] <> '/' and IsReadableFile( Concatenation( pkgdir, x ) ) ) );
2173    IsFilenameList:= x -> IsList( x ) and ForAll( x, IsFilename );
2174    IsURL := x -> ForAny(["http://","https://","ftp://"], s -> StartsWith(x,s));
2175
2176    result:= true;
2177
2178    TestOption:= function( record, name, type, typename )
2179    if IsBound( record.( name ) ) and not type( record.( name ) ) then
2180      Print( "#E  component `", name, "', if present, must be bound to ",
2181             typename, "\n" );
2182      result:= false;
2183      return false;
2184    fi;
2185    return true;
2186    end;
2187
2188    TestMandat:= function( record, name, type, typename )
2189    if not IsBound( record.( name ) ) or not type( record.( name ) ) then
2190      Print( "#E  component `", name, "' must be bound to ",
2191             typename, "\n" );
2192      result:= false;
2193      return false;
2194    fi;
2195    return true;
2196    end;
2197
2198    TestMandat( record, "PackageName",
2199        x -> IsString(x) and 0 < Length(x),
2200        "a nonempty string" );
2201    TestMandat( record, "Subtitle", IsString, "a string" );
2202    TestMandat( record, "Version",
2203        x -> IsString(x) and 0 < Length(x) and x[1] <> '=',
2204        "a nonempty string that does not start with `='" );
2205    TestMandat( record, "Date",
2206        x -> IsString(x) and Length(x) = 10 and x{ [3,6] } = "//"
2207                 and ForAll( x{ [1,2,4,5,7,8,9,10] }, IsDigitChar ),
2208        "a string of the form `dd/mm/yyyy'" );
2209    TestOption( record, "License",
2210        x -> IsString(x) and 0 < Length(x),
2211        "a nonempty string containing an SPDX ID" );
2212    TestMandat( record, "ArchiveURL", IsURL, "a string started with http://, https:// or ftp://" );
2213    TestMandat( record, "ArchiveFormats", IsString, "a string" );
2214    TestOption( record, "TextFiles", IsStringList, "a list of strings" );
2215    TestOption( record, "BinaryFiles", IsStringList, "a list of strings" );
2216    TestOption( record, "TextBinaryFilesPatterns",
2217        x -> IsStringList(x) and
2218             ForAll( x, i -> Length(i) > 0 ) and
2219             ForAll( x, i -> i[1] in ['T','B'] ),
2220        "a list of strings, each started with 'T' or 'B'" );
2221    if Number( [ IsBound(record.TextFiles),
2222                 IsBound(record.BinaryFiles),
2223                 IsBound(record.TextBinaryFilesPatterns) ],
2224               a -> a=true ) > 1 then
2225      Print("#W  only one of TextFiles, BinaryFiles or TextBinaryFilesPatterns\n");
2226      Print("#W  components must be bound\n");
2227    fi;
2228    if     TestOption( record, "Persons", IsRecordList, "a list of records" )
2229       and IsBound( record.Persons ) then
2230      for subrec in record.Persons do
2231        TestMandat( subrec, "LastName", IsString, "a string" );
2232        TestMandat( subrec, "FirstNames", IsString, "a string" );
2233        if not (    IsBound( subrec.IsAuthor )
2234                 or IsBound( subrec.IsMaintainer ) ) then
2235          Print( "#E  one of the components `IsAuthor', `IsMaintainer' ",
2236                 "must be bound\n" );
2237          result:= false;
2238        fi;
2239        TestOption( subrec, "IsAuthor", IsProperBool, "`true' or `false'" );
2240        TestOption( subrec, "IsMaintainer", IsProperBool,
2241            "`true' or `false'" );
2242        if IsBound( subrec.IsMaintainer ) then
2243          if subrec.IsMaintainer = true and
2244               not ( IsBound( subrec.Email ) or
2245                     IsBound( subrec.WWWHome ) or
2246                     IsBound( subrec.PostalAddress ) ) then
2247            Print( "#E  one of the components `Email', `WWWHome', `PostalAddress'\n",
2248                   "#E  must be bound for each package maintainer \n" );
2249            result:= false;
2250          fi;
2251        fi;
2252        TestOption( subrec, "Email", IsString, "a string" );
2253        TestOption( subrec, "WWWHome", IsURL, "a string started with http://, https:// or ftp://" );
2254        TestOption( subrec, "PostalAddress", IsString, "a string" );
2255        TestOption( subrec, "Place", IsString, "a string" );
2256        TestOption( subrec, "Institution", IsString, "a string" );
2257      od;
2258    fi;
2259
2260    if TestMandat( record, "Status",
2261           x -> x in [ "accepted", "submitted", "deposited", "dev", "other" ],
2262           "one of \"accepted\", \"deposited\", \"dev\", \"other\"" )
2263       and record.Status = "accepted" then
2264      TestMandat( record, "CommunicatedBy",
2265          x -> IsString(x) and PositionSublist( x, " (" ) <> fail
2266                   and x[ Length(x) ] = ')',
2267          "a string of the form `<name> (<place>)'" );
2268      TestMandat( record, "AcceptDate",
2269          x -> IsString( x ) and Length( x ) = 7 and x[3] = '/'
2270                   and ForAll( x{ [1,2,4,5,6,7] }, IsDigitChar ),
2271          "a string of the form `mm/yyyy'" );
2272    fi;
2273    TestMandat( record, "README_URL", IsURL, "a string started with http://, https:// or ftp://" );
2274    TestMandat( record, "PackageInfoURL", IsURL, "a string started with http://, https:// or ftp://" );
2275
2276    if TestOption( record, "SourceRepository", IsRecord, "a record" ) then
2277      if IsBound( record.SourceRepository ) then
2278        TestMandat( record.SourceRepository, "Type", IsString, "a string" );
2279        TestMandat( record.SourceRepository, "URL", IsString, "a string" );
2280      fi;
2281    fi;
2282    TestOption( record, "IssueTrackerURL", IsURL, "a string started with http://, https:// or ftp://" );
2283    TestOption( record, "SupportEmail", IsString, "a string" );
2284    TestMandat( record, "AbstractHTML", IsString, "a string" );
2285    TestMandat( record, "PackageWWWHome", IsURL, "a string started with http://, https:// or ftp://" );
2286    if TestMandat( record, "PackageDoc",
2287           x -> IsRecord( x ) or IsRecordList( x ),
2288           "a record or a list of records" ) then
2289      if IsRecord( record.PackageDoc ) then
2290        list:= [ record.PackageDoc ];
2291      else
2292        list:= record.PackageDoc;
2293      fi;
2294      for subrec in list do
2295        TestMandat( subrec, "BookName", IsString, "a string" );
2296        if IsBound(subrec.Archive) then
2297          Print("#W  PackageDoc.Archive is withdrawn, use PackageDoc.ArchiveURLSubset instead\n");
2298        fi;
2299        TestMandat( subrec, "ArchiveURLSubset", IsFilenameList,
2300            "a list of strings denoting relative paths to readable files or directories" );
2301        TestMandat( subrec, "HTMLStart", IsFilename,
2302                    "a string denoting a relative path to a readable file" );
2303        TestMandat( subrec, "PDFFile", IsFilename,
2304                    "a string denoting a relative path to a readable file" );
2305        TestMandat( subrec, "SixFile", IsFilename,
2306                    "a string denoting a relative path to a readable file" );
2307        TestMandat( subrec, "LongTitle", IsString, "a string" );
2308      od;
2309    fi;
2310    if     TestOption( record, "Dependencies", IsRecord, "a record" )
2311       and IsBound( record.Dependencies ) then
2312      TestOption( record.Dependencies, "GAP", IsString, "a string" );
2313      TestOption( record.Dependencies, "NeededOtherPackages",
2314          comp -> IsList( comp ) and ForAll( comp,
2315                      l -> IsList( l ) and Length( l ) = 2
2316                                       and ForAll( l, IsString ) ),
2317          "a list of pairs `[ <pkgname>, <pkgversion> ]' of strings" );
2318      TestOption( record.Dependencies, "SuggestedOtherPackages",
2319          comp -> IsList( comp ) and ForAll( comp,
2320                      l -> IsList( l ) and Length( l ) = 2
2321                                       and ForAll( l, IsString ) ),
2322          "a list of pairs `[ <pkgname>, <pkgversion> ]' of strings" );
2323      TestOption( record.Dependencies, "ExternalConditions",
2324          comp -> IsList( comp ) and ForAll( comp,
2325                      l -> IsString( l ) or ( IsList( l ) and Length( l ) = 2
2326                                      and ForAll( l, IsString ) ) ),
2327          "a list of strings or of pairs `[ <text>, <URL> ]' of strings" );
2328
2329      # If the package is a needed package of GAP then all its needed
2330      # packages must also occur in the list of needed packages of GAP.
2331      list:= List( GAPInfo.Dependencies.NeededOtherPackages,
2332                   x -> LowercaseString( x[1] ) );
2333      if     IsBound( record.PackageName )
2334         and IsString( record.PackageName )
2335         and LowercaseString( record.PackageName ) in list
2336         and IsBound( record.Dependencies.NeededOtherPackages )
2337         and IsList( record.Dependencies.NeededOtherPackages ) then
2338        list:= Filtered( record.Dependencies.NeededOtherPackages,
2339                         x ->     IsList( x ) and IsBound( x[1] )
2340                              and IsString( x[1] )
2341                              and not LowercaseString( x[1] ) in list );
2342        if not IsEmpty( list ) then
2343          Print( "#E  the needed packages in '",
2344                 List( list, x -> x[1] ), "'\n",
2345                 "#E  are currently not needed packages of GAP\n" );
2346          result:= false;
2347        fi;
2348      fi;
2349    fi;
2350    TestMandat( record, "AvailabilityTest", IsFunction, "a function" );
2351    TestOption( record, "BannerFunction", IsFunction, "a function" );
2352    TestOption( record, "BannerString", IsString, "a string" );
2353    TestOption( record, "TestFile", IsFilename,
2354                "a string denoting a relative path to a readable file" );
2355    TestOption( record, "Keywords", IsStringList, "a list of strings" );
2356
2357    return result;
2358    end );
2359
2360
2361#############################################################################
2362##
2363#V  GAPInfo.PackagesRestrictions
2364##
2365##  <ManSection>
2366##  <Var Name="GAPInfo.PackagesRestrictions"/>
2367##
2368##  <Description>
2369##  This is a mutable record, each component being the name of a package
2370##  <A>pkg</A> (in lowercase letters) that is required/recommended to be
2371##  updated to a certain version,
2372##  the value being a record with the following components.
2373##  <P/>
2374##  <List>
2375##  <Mark><C>OnInitialization</C></Mark>
2376##  <Item>
2377##      a function that takes one argument, the record stored in the
2378##      <F>PackageInfo.g</F> file of the package,
2379##      and returns <K>true</K> if the package can be loaded,
2380##      and returns <K>false</K> if not.
2381##      The function is allowed to change components of the argument record.
2382##      It should not print any message,
2383##      this should be left to the <C>OnLoad</C> component,
2384##  </Item>
2385##  <Mark><C>OnLoad</C></Mark>
2386##  <Item>
2387##      a function that takes one argument, the record stored in the
2388##      <F>PackageInfo.g</F> file of the package, and can print a message
2389##      when the availability of the package is checked for the first time;
2390##      this message is intended to explain why the package cannot loaded due
2391##      to the <K>false</K> result of the <C>OnInitialization</C> component,
2392##      or as a warning about known problems (when the package is in fact
2393##      loaded), and it might give hints for upgrading the package.
2394##  </Item>
2395##  </List>
2396##  </Description>
2397##  </ManSection>
2398##
2399GAPInfo.PackagesRestrictions := AtomicRecord(rec(
2400  anupq := MakeImmutable(rec(
2401    OnInitialization := function( pkginfo )
2402        if CompareVersionNumbers( pkginfo.Version, "1.3" ) = false then
2403          return false;
2404        fi;
2405        return true;
2406        end,
2407    OnLoad := function( pkginfo )
2408        if CompareVersionNumbers( pkginfo.Version, "1.3" ) = false then
2409          Print( "  The package `anupq'",
2410              " should better be upgraded at least to version 1.3,\n",
2411              "  the given version (", pkginfo.Version,
2412              ") is known to be incompatible\n",
2413              "  with the current version of GAP.\n",
2414              "  It is strongly recommended to update to the ",
2415              "most recent version, see URL\n",
2416              "      http://www.math.rwth-aachen.de/~Greg.Gamble/ANUPQ\n" );
2417        fi;
2418        end )),
2419
2420  autpgrp := MakeImmutable(rec(
2421    OnInitialization := function( pkginfo )
2422        return true;
2423        end,
2424    OnLoad := function( pkginfo )
2425        if CompareVersionNumbers( pkginfo.Version, "1.1" ) = false then
2426          Print( "  The package `autpgrp'",
2427              " should better be upgraded at least to version 1.1,\n",
2428              "  the given version (", pkginfo.Version,
2429              ") is known to be incompatible\n",
2430              "  with the current version of GAP.\n",
2431              "  It is strongly recommended to update to the ",
2432              "most recent version, see URL\n",
2433              "      https://www.gap-system.org/Packages/autpgrp.html\n" );
2434        fi;
2435        end )) ));
2436
2437
2438#############################################################################
2439##
2440#F  SuggestUpgrades( versions ) . . compare installed with distributed versions
2441##
2442InstallGlobalFunction( SuggestUpgrades, function( suggestedversions )
2443    local ok, outstr, out, entry, inform, info;
2444
2445    suggestedversions := Set( List( suggestedversions, ShallowCopy ) );
2446    ok:= true;
2447    # We collect the output in a string, because availability test may
2448    # cause some intermediate printing. This way the output of the present
2449    # function comes after such texts.
2450    outstr := "";
2451    out := OutputTextString(outstr, true);
2452    PrintTo(out, "#I ======================================================",
2453                 "================ #\n",
2454                 "#I      Result of 'SuggestUpgrades':\n#I\n"
2455                 );
2456    # Deal with the kernel and library versions.
2457    entry:= First( suggestedversions, x -> x[1] = "GAPLibrary" );
2458    if entry = fail then
2459      PrintTo(out,  "#E  no info about suggested GAP library version ...\n" );
2460      ok:= false;
2461    elif not CompareVersionNumbers( GAPInfo.Version, entry[2] ) then
2462      PrintTo(out,  "#E  You are using version ", GAPInfo.Version,
2463             " of the GAP library.\n",
2464             "#E  Please upgrade to version ", entry[2], ".\n\n" );
2465      ok:= false;
2466    elif not CompareVersionNumbers( entry[2], GAPInfo.Version ) then
2467      PrintTo(out,  "#E  You are using version ", GAPInfo.Version,
2468             " of the GAP library.\n",
2469             "#E  This is newer than the distributed version ",
2470             entry[2], ".\n\n" );
2471      ok:= false;
2472    fi;
2473    RemoveSet( suggestedversions, entry );
2474
2475    entry:= First( suggestedversions, x -> x[1] = "GAPKernel" );
2476    if entry = fail then
2477      PrintTo(out,  "#E  no info about suggested GAP kernel version ...\n" );
2478      ok:= false;
2479    elif not CompareVersionNumbers( GAPInfo.KernelVersion, entry[2] ) then
2480      PrintTo(out,  "#E  You are using version ", GAPInfo.KernelVersion,
2481             " of the GAP kernel.\n",
2482             "#E  Please upgrade to version ", entry[2], ".\n\n" );
2483      ok:= false;
2484    elif not CompareVersionNumbers( entry[2], GAPInfo.KernelVersion ) then
2485      PrintTo(out,  "#E  You are using version ", GAPInfo.KernelVersion,
2486             " of the GAP kernel.\n",
2487             "#E  This is newer than the distributed version ",
2488             entry[2], ".\n\n" );
2489      ok:= false;
2490    fi;
2491    RemoveSet( suggestedversions, entry );
2492
2493    # Deal with present packages which are not distributed.
2494    inform := Difference(NamesOfComponents(GAPInfo.PackagesInfo),
2495              List(suggestedversions, x-> LowercaseString(x[1])));
2496    if not IsEmpty( inform ) then
2497      PrintTo(out,  "#I  The following GAP packages are present but not ",
2498                    "officially distributed.\n" );
2499      for entry in inform do
2500        info := GAPInfo.PackagesInfo.(entry)[1];
2501        PrintTo(out,  "#I    ", info.PackageName, " ", info.Version, "\n" );
2502      od;
2503      PrintTo(out,  "\n" );
2504      ok:= false;
2505    fi;
2506
2507
2508    # Deal with packages that are not installed.
2509    inform := Filtered( suggestedversions, entry -> not IsBound(
2510                   GAPInfo.PackagesInfo.( LowercaseString( entry[1] ) ) )
2511                 and ForAll( GAPInfo.PackagesInfoRefuseLoad,
2512                             r -> LowercaseString( entry[1] )
2513                                  <> LowercaseString( r.PackageName ) ) );
2514    if not IsEmpty( inform ) then
2515      PrintTo(out,  "#I  The following distributed GAP packages are ",
2516                    "not installed.\n" );
2517      for entry in inform do
2518        PrintTo(out,  "#I    ", entry[1], " ", entry[2], "\n" );
2519      od;
2520      PrintTo(out,  "\n" );
2521      ok:= false;
2522    fi;
2523    SubtractSet( suggestedversions, inform );
2524
2525    # Deal with packages whose installed versions are not available
2526    # (without saying anything about the reason).
2527#T Here it would be desirable to omit those packages that cannot be loaded
2528#T on the current platform; e.g., Windoofs users need not be informed about
2529#T packages for which no Windoofs version is available.
2530    # These packages can be up to date or outdated.
2531    for entry in suggestedversions do
2532      Add( entry, InstalledPackageVersion( entry[1] ) );
2533#T Here we may get print statements from the availability testers;
2534#T how to avoid this?
2535    od;
2536    inform:= Filtered( suggestedversions, entry -> entry[3] = fail );
2537    if not IsEmpty( inform ) then
2538      PrintTo(out,  "#I  The following GAP packages are present ",
2539             "but cannot be used.\n" );
2540      for entry in inform do
2541        PrintTo(out,  "#I    ", entry[1], " ",
2542             GAPInfo.PackagesInfo.( LowercaseString( entry[1] ) )[1].Version,
2543             "\n" );
2544        if not ForAny( GAPInfo.PackagesInfo.( LowercaseString( entry[1] ) ),
2545                   r -> CompareVersionNumbers( r.Version, entry[2] ) ) then
2546          PrintTo(out,  "#I         (distributed version is newer:   ",
2547                   entry[2], ")\n" );
2548        fi;
2549      od;
2550      PrintTo(out, "\n" );
2551      ok:= false;
2552    fi;
2553    SubtractSet( suggestedversions, inform );
2554
2555    # Deal with packages in *newer* (say, dev-) versions than the
2556    # distributed ones.
2557    inform:= Filtered( suggestedversions, entry -> not CompareVersionNumbers(
2558                 entry[2], entry[3] ) );
2559    if not IsEmpty( inform ) then
2560      PrintTo(out,
2561             "#I  Your following GAP packages are *newer* than the ",
2562             "distributed version.\n" );
2563      for entry in inform do
2564        PrintTo(out,  "#I    ", entry[1], " ", entry[3],
2565               " (distributed is ", entry[2], ")\n" );
2566      od;
2567      PrintTo(out,  "\n" );
2568      ok:= false;
2569    fi;
2570    # Deal with packages whose installed versions are not up to date.
2571    inform:= Filtered( suggestedversions, entry -> not CompareVersionNumbers(
2572                 entry[3], entry[2] ) );
2573    if not IsEmpty( inform ) then
2574      PrintTo(out,
2575             "#I  The following GAP packages are available but outdated.\n" );
2576      for entry in inform do
2577        PrintTo(out,  "#I    ", entry[1], " ", entry[3],
2578               " (please upgrade to ", entry[2], ")\n" );
2579      od;
2580      PrintTo(out,  "\n" );
2581      ok:= false;
2582    fi;
2583
2584    if ok then
2585      PrintTo(out,  "#I  Your GAP installation is up to date with the ",
2586      "official distribution.\n\n" );
2587    fi;
2588    CloseStream(out);
2589    Print( outstr );
2590    end );
2591
2592
2593#############################################################################
2594##
2595#F  BibEntry( "GAP"[, <key>] )
2596#F  BibEntry( <pkgname>[, <key>] )
2597#F  BibEntry( <pkginfo>[, <key>] )
2598##
2599Unicode:= "dummy";
2600Encode:= "dummy";
2601
2602InstallGlobalFunction( BibEntry, function( arg )
2603    local key, pkgname, pkginfo, GAP, ps, months, val, entry, author;
2604
2605    key:= false;
2606    if   Length( arg ) = 1 and IsString( arg[1] ) then
2607      pkgname:= arg[1];
2608    elif Length( arg ) = 2 and IsString( arg[1] ) and IsString( arg[2] ) then
2609      pkgname:= arg[1];
2610      key:= arg[2];
2611    elif Length( arg ) = 1 and IsRecord( arg[1] ) then
2612      pkginfo:= arg[1];
2613    elif Length( arg ) = 2 and IsRecord( arg[1] ) and IsString( arg[2] ) then
2614      pkginfo:= arg[1];
2615      key:= arg[2];
2616    else
2617      Error( "usage: BibEntry( \"GAP\"[, <key>] ), ",
2618             "BibEntry( <pkgname>[, <key>] ), ",
2619             "BibEntry( <pkginfo>[, <key>] )" );
2620    fi;
2621
2622    GAP:= false;
2623    if IsBound( pkgname ) then
2624      if pkgname = "GAP" then
2625        GAP:= true;
2626      else
2627        pkginfo:= InstalledPackageVersion( pkgname );
2628        pkginfo:= First( PackageInfo( pkgname ), r -> r.Version = pkginfo );
2629        if pkginfo = fail then
2630          return "";
2631        fi;
2632      fi;
2633    fi;
2634
2635    if key = false then
2636      if GAP then
2637        key:= Concatenation( "GAP", GAPInfo.Version );
2638      elif IsBound( pkginfo.Version ) then
2639        key:= Concatenation( pkginfo.PackageName, pkginfo.Version );
2640      else
2641        key:= pkginfo.PackageName;
2642      fi;
2643    fi;
2644
2645    ps:= function( str )
2646      local uni;
2647
2648      uni:= Unicode( str, "UTF-8" );
2649      if uni = fail then
2650        uni:= Unicode( str, "ISO-8859-1" );
2651      fi;
2652      return Encode( uni, GAPInfo.TermEncoding );
2653    end;
2654
2655    # According to <Cite Key="La85"/>,
2656    # the supported fields of a Bib&TeX; entry of <C>@misc</C> type are
2657    # the following.
2658    # <P/>
2659    # <List>
2660    # <Mark><C>author</C></Mark>
2661    # <Item>
2662    #   computed from the <C>Persons</C> component of the package,
2663    #   not distinguishing authors and maintainers,
2664    #   keeping the ordering of entries,
2665    # </Item>
2666    # <Mark><C>title</C></Mark>
2667    # <Item>
2668    #   computed from the <C>PackageName</C> and <C>Subtitle</C> components
2669    #   of the package,
2670    # </Item>
2671    # <Mark><C>month</C> and <C>year</C></Mark>
2672    # <Item>
2673    #   computed from the <C>Date</C> component of the package,
2674    # </Item>
2675    # <Mark><C>note</C></Mark>
2676    # <Item>
2677    #   the string <C>"Refereed \\textsf{GAP} package"</C> or
2678    #   <C>"\\textsf{GAP} package"</C>,
2679    # </Item>
2680    # <Mark><C>howpublished</C></Mark>
2681    # <Item>
2682    #   the <C>PackageWWWHome</C> component of the package.
2683    # </Item>
2684    # </List>
2685    # <P/>
2686    # Also the <C>edition</C> component seems to be supported;
2687    # it is computed from the <C>Version</C> component of the package.
2688
2689    # Bib&Tex;'s <C>@manual</C> type seems to be not appropriate,
2690    # since this type does not support a URL component
2691    # in the base bib styles of La&TeX;.
2692    # Instead we can use the <C>@misc</C> type and its <C>howpublished</C>
2693    # component.
2694    # We put the version information into the <C>title</C> component since
2695    # the <C>edition</C> component is not supported in the base styles.
2696
2697    months:= [ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
2698               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];
2699    if GAP then
2700      val:= SplitString( GAPInfo.Date, "-" );
2701      if Length( val ) = 3 then
2702        if Int( val[2] ) in [ 1 .. 12 ] then
2703          val:= Concatenation( "  <month>", months[ Int( val[2] ) ],
2704                               "</month>\n  <year>", val[3], "</year>\n" );
2705        else
2706          val:= Concatenation( "  <month>", val[2],
2707                               "</month>\n  <year>", val[3], "</year>\n" );
2708        fi;
2709      else
2710        val:= "";
2711      fi;
2712      entry:= Concatenation(
2713        "<entry id=\"", key, "\"><misc>\n",
2714        "  <title><C>GAP</C> &ndash;",
2715        " <C>G</C>roups, <C>A</C>lgorithms,\n",
2716        "         and <C>P</C>rogramming,",
2717        " <C>V</C>ersion ", GAPInfo.Version, "</title>\n",
2718        "  <howpublished><URL>https://www.gap-system.org</URL></howpublished>\n",
2719        val,
2720        "  <key>GAP</key>\n",
2721        "  <keywords>groups; *; gap; manual</keywords>\n",
2722        "  <other type=\"organization\">The GAP <C>G</C>roup</other>\n",
2723        "</misc></entry>" );
2724    else
2725      entry:= Concatenation( "<entry id=\"", key, "\"><misc>\n" );
2726      author:= List( Filtered( pkginfo.Persons,
2727        person -> person.IsAuthor or person.IsMaintainer ),
2728          person -> Concatenation(
2729            "    <name><first>", person.FirstNames,
2730            "</first><last>", person.LastName, "</last></name>\n" ) );
2731      if not IsEmpty( author ) then
2732        Append( entry, Concatenation(
2733          "  <author>\n",
2734          ps( Concatenation( author ) ),
2735          "  </author>\n" ) );
2736      fi;
2737      Append( entry, Concatenation(
2738        "  <title><C>", pkginfo.PackageName, "</C>" ) );
2739      if IsBound( pkginfo.Subtitle ) then
2740        Append( entry, Concatenation(
2741          ", ", ps( pkginfo.Subtitle ) ) );
2742      fi;
2743      if IsBound( pkginfo.Version ) then
2744        Append( entry, Concatenation(
2745          ",\n         <C>V</C>ersion ", pkginfo.Version ) );
2746      fi;
2747      Append( entry, "</title>\n" );
2748      if IsBound( pkginfo.PackageWWWHome ) then
2749        Append( entry, Concatenation(
2750          "  <howpublished><URL>", pkginfo.PackageWWWHome,
2751          "</URL></howpublished>\n" ) );
2752      fi;
2753      if IsBound( pkginfo.Date ) and IsDenseList( pkginfo.Date )
2754                                 and Length( pkginfo.Date ) = 10 then
2755        if Int( pkginfo.Date{ [ 4, 5 ] } ) in [ 1 .. 12 ] then
2756          Append( entry, Concatenation(
2757            "  <month>", months[ Int( pkginfo.Date{ [ 4, 5 ] } ) ],
2758            "</month>\n",
2759            "  <year>", pkginfo.Date{ [ 7 .. 10 ] }, "</year>\n" ) );
2760        else
2761          Append( entry, Concatenation(
2762            "  <month>", pkginfo.Date{ [ 4, 5 ] }, "</month>\n",
2763            "  <year>", pkginfo.Date{ [ 7 .. 10 ] }, "</year>\n" ) );
2764        fi;
2765      fi;
2766      if IsBound( pkginfo.Status ) and pkginfo.Status = "accepted" then
2767        Append( entry, "  <note>Refereed " );
2768      else
2769        Append( entry, "  <note>" );
2770      fi;
2771#     Append( entry, "<Package>GAP</Package> package</note>\n" );
2772      Append( entry, "GAP package</note>\n" );
2773      if IsBound( pkginfo.Keywords ) then
2774        Append( entry, Concatenation(
2775          "  <keywords>",
2776          JoinStringsWithSeparator( pkginfo.Keywords, "; " ),
2777          "</keywords>\n" ) );
2778      fi;
2779      Append( entry, "</misc></entry>" );
2780    fi;
2781
2782    return entry;
2783end );
2784
2785Unbind( Unicode );
2786Unbind( Encode );
2787
2788# dummy assignments to functions to be read lated in the GAPDoc package
2789ParseBibXMLextString:= "dummy";
2790StringBibXMLEntry:= "dummy";
2791
2792InstallGlobalFunction( Cite, function(arg)
2793local name, bib, key, parse, year;
2794if Length(arg)=0 then
2795  name:="GAP";
2796else
2797  name := NormalizedWhitespace(arg[1]);
2798fi;
2799if name="gap" then
2800  name:="GAP";
2801fi;
2802if Length(arg)<=1 then
2803  bib:= BibEntry( name );
2804elif Length(arg)>2 then
2805  Error("`Cite' takes no more than two arguments");
2806else
2807  key:=arg[2];
2808  bib:= BibEntry( name, key );
2809fi;
2810if bib="" then
2811  Print("WARNING: No working version of package ", name, " is available!\n");
2812  return;
2813fi;
2814parse:= ParseBibXMLextString( bib );
2815Print("Please use one of the following samples\n",
2816      "to cite ", name, " version from this installation\n\n");
2817
2818Print("Text:\n\n");
2819Print( StringBibXMLEntry( parse.entries[1], "Text" ) );
2820
2821Print("HTML:\n\n");
2822Print( StringBibXMLEntry( parse.entries[1], "HTML" ) );
2823
2824Print("BibXML:\n\n");
2825Print( bib, "\n\n" );
2826
2827Print("BibTeX:\n\n");
2828Print( StringBibXMLEntry( parse.entries[1], "BibTeX" ), "\n" );
2829
2830if name="GAP" then
2831  year:=SplitString(GAPInfo.Date,"-");
2832  if Length(year)=3 then
2833    year:=year[3];
2834  else
2835    year:=year[1]; # to work in GAP.dev
2836  fi;
2837
2838  Print("If you are not using BibTeX, here is the bibliography entry produced \n",
2839        "by BibTeX (in bibliography style `alpha'):\n\n",
2840        "\\bibitem[GAP]{GAP4}\n",
2841        "\\emph{GAP -- Groups, Algorithms, and Programming}, ",
2842        "Version ", GAPInfo.Version, ",\n",
2843        "The GAP~Group (", year, "), \\verb+https://www.gap-system.org+.\n\n");
2844  Print(
2845  "If you have (predominantly) used one or more particular GAP packages,\n",
2846  "please cite these packages in addition to GAP itself (either check the\n",
2847  "the package documentation for the suggestions, or use a scheme like:\n\n",
2848
2849  "[PKG]\n",
2850  "<Author name(s)>, <package name>, <package long title>, \n",
2851  "Version <package version> (<package date>), (GAP package),\n",
2852  "<package URL>.\n\n",
2853
2854  "You may also produce citation samples for a GAP package by entering\n\n",
2855  "    Cite(\"packagename\");\n\n",
2856  "in a GAP installation with the working version of this package available.\n\n");
2857fi;
2858end);
2859
2860Unbind( ParseBibXMLextString );
2861Unbind( StringBibXMLEntry );
2862
2863
2864#############################################################################
2865##
2866#F  PackageVariablesInfo( <pkgname>, <version> )
2867##
2868NamesSystemGVars := "dummy";   # is not yet defined when this file is read
2869NamesUserGVars   := "dummy";
2870
2871InstallGlobalFunction( PackageVariablesInfo, function( pkgname, version )
2872    local test, cache, cache2, PkgName, realname, new, new_up_to_case,
2873          redeclared, newmethod, pos, key_dependent_operation, rules,
2874          localBindGlobal, rule, loaded, pkg, args, docmark, done, result,
2875          subrule, added, prev, subresult, entry, isrelevant, guesssource,
2876          protected;
2877
2878    pkgname:= LowercaseString( pkgname );
2879    test:= TestPackageAvailability( pkgname, version );
2880
2881    # If the function has been called for this package then
2882    # return the stored value.
2883    cache:= Concatenation( pkgname, ":", version );
2884    if not IsBound( GAPInfo.PackageVariablesInfo ) then
2885      GAPInfo.PackageVariablesInfo:= rec();
2886    elif IsBound( GAPInfo.PackageVariablesInfo.( cache ) ) then
2887      return GAPInfo.PackageVariablesInfo.( cache );
2888    elif version = "" and test = true then
2889      cache2:= Concatenation( pkgname, ":",
2890                   InstalledPackageVersion( pkgname ) );
2891      if IsBound( GAPInfo.PackageVariablesInfo.( cache2 ) ) then
2892        return GAPInfo.PackageVariablesInfo.( cache2 );
2893      fi;
2894    fi;
2895
2896    # Check that the package is available but not yet loaded.
2897    if test = true then
2898      Info( InfoWarning, 1,
2899            "the package `", pkgname, "' is already loaded" );
2900      return [];
2901    elif test = fail then
2902      if version = "" then
2903        Info( InfoWarning, 1,
2904              "the package `", pkgname, "' cannot be loaded" );
2905      else
2906        Info( InfoWarning, 1,
2907              "the package `", pkgname, "' cannot be loaded in version `",
2908              version, "'" );
2909      fi;
2910      return [];
2911    fi;
2912
2913    PkgName:= GAPInfo.PackagesInfo.( pkgname )[1].PackageName;
2914
2915    realname:= function( name )
2916        if name[ Length( name ) ] = '@' then
2917          return Concatenation( name, PkgName );
2918        else
2919          return name;
2920        fi;
2921    end;
2922
2923    new:= function( entry )
2924        local name;
2925
2926        name:= realname( entry[1][1] );
2927        if not name in GAPInfo.data.varsThisPackage then
2928          return fail;
2929        elif Length( entry[1] ) = 3 and entry[1][3] = "mutable"
2930             and Length( name  ) > 9 and name{ [ 1 .. 8 ] } = "Computed"
2931             and name[ Length( name ) ] = 's'
2932             and IsBoundGlobal( name{ [ 9 .. Length( name ) - 1 ] } ) then
2933          return fail;
2934        elif Length( entry[1] ) = 2
2935             and Length( name  ) > 3 and name{ Length( name ) - [1,0] } = "Op"
2936             and IsBoundGlobal( name{ [ 1 .. Length( name ) - 2 ] } )
2937             and ForAny( GAPInfo.data.KeyDependentOperation[2],
2938                         x -> x[1][1] = name{ [ 1 .. Length( name ) - 2 ] }
2939                              and x[2] = entry[2]
2940                              and x[3] = entry[3] ) then
2941          # Ignore the declaration of the operation created by
2942          # `KeyDependentOperation'.
2943          # (We compare filename and line number in the file with these values
2944          # for the call of `KeyDependentOperation'.)
2945          return fail;
2946        else
2947          return [ name, ValueGlobal( name ), entry[2], entry[3] ];
2948        fi;
2949      end;
2950
2951    new_up_to_case:= function( entry )
2952        local name;
2953
2954        name:= realname( entry[1][1] );
2955        if   not name in GAPInfo.data.varsThisPackage then
2956          return fail;
2957        elif LowercaseString( name ) in GAPInfo.data.lowercase_vars then
2958          return [ name, ValueGlobal( name ), entry[2], entry[3] ];
2959        else
2960          return fail;
2961        fi;
2962      end;
2963
2964    redeclared:= function( entry )
2965        local name;
2966
2967        name:= realname( entry[1][1] );
2968        if   not name in GAPInfo.data.varsThisPackage then
2969          return [ name, ValueGlobal( name ), entry[2], entry[3] ];
2970        else
2971          return fail;
2972        fi;
2973      end;
2974
2975    newmethod:= function( entry )
2976      local name, setter, getter;
2977
2978      name:= NameFunction( entry[1][1] );
2979      if IsString( entry[1][2] ) then
2980        if entry[1][2] in [ "system setter", "system mutable setter",
2981                            "default method, does nothing" ] then
2982          setter:= entry[1][1];
2983          if ForAny( ATTRIBUTES,
2984                     attr -> IsIdenticalObj( setter, attr[4] ) ) then
2985            return fail;
2986          fi;
2987        elif entry[1][2] in [ "system getter",
2988          "default method requiring categories and checking properties" ] then
2989          getter:= entry[1][1];
2990          if ForAny( ATTRIBUTES,
2991                     attr -> IsIdenticalObj( getter, attr[3] ) ) then
2992            return fail;
2993          fi;
2994        elif entry[1][2] in [ "default method" ] then
2995          # Ignore the default methods (for attribute and operation)
2996          # that are installed in calls to `KeyDependentOperation'.
2997          # (We compare filename and line number in the file
2998          # with these values for the call of `KeyDependentOperation'.)
2999          if 9 < Length( name  ) and name{ [ 1 .. 8 ] } = "Computed"
3000             and name[ Length( name ) ] = 's'
3001             and IsBoundGlobal( name{ [ 9 .. Length( name ) - 1 ] } )
3002             and ForAny( GAPInfo.data.KeyDependentOperation[2],
3003                         x -> x[1][1] = name{ [ 9 .. Length( name ) - 1 ] }
3004                              and x[2] = entry[2]
3005                              and x[3] = entry[3] ) then
3006            return fail;
3007          elif IsBoundGlobal( name )
3008               and ForAny( GAPInfo.data.KeyDependentOperation[2],
3009                           x -> x[1][1] = name
3010                                and x[2] = entry[2]
3011                                and x[3] = entry[3] ) then
3012            return fail;
3013          fi;
3014        fi;
3015      fi;
3016
3017      # Omit methods for `FlushCaches'.
3018      if name = "FlushCaches" then
3019        return fail;
3020      fi;
3021
3022      # Extract a comment if possible.
3023      if IsString( entry[1][2] ) then
3024        # Store also the comment for this method installation.
3025        return [ name, entry[1][ Length( entry[1] ) ],
3026                 entry[2], entry[3], entry[1][2] ];
3027      else
3028        pos:= PositionProperty( entry[1],
3029                                x -> IsList( x ) and not IsEmpty( x )
3030                                     and ForAll( x, IsString ) );
3031        if pos <> fail then
3032          # Create a comment from the list of strings that describe filters.
3033          return [ NameFunction( entry[1][1] ),
3034                   entry[1][ Length( entry[1] ) ],
3035                   entry[2], entry[3], Concatenation( "for ",
3036                   JoinStringsWithSeparator( entry[1][ pos ], ", " ) ) ];
3037        else
3038          # We know no comment.
3039          return [ NameFunction( entry[1][1] ),
3040                   entry[1][ Length( entry[1] ) ],
3041                   entry[2], entry[3] ];
3042        fi;
3043      fi;
3044      end;
3045
3046    key_dependent_operation:= function( entry )
3047      return entry;
3048      end;
3049
3050    # List the cases to be dealt with.
3051    rules:= [
3052      [ "DeclareGlobalFunction",
3053        [ "new global functions", new ],
3054        [ "globals that are new only up to case", new_up_to_case ] ],
3055      [ "DeclareGlobalVariable",
3056        [ "new global variables", new ],
3057        [ "globals that are new only up to case", new_up_to_case ] ],
3058      [ "BindGlobal",
3059        [ "new global variables", new ],
3060        [ "globals that are new only up to case", new_up_to_case ] ],
3061      [ "DeclareOperation",
3062        [ "new operations", new ],
3063        [ "redeclared operations", redeclared ],
3064        [ "globals that are new only up to case", new_up_to_case ] ],
3065      [ "DeclareAttribute",
3066        [ "new attributes", new ],
3067        [ "redeclared attributes", redeclared ],
3068        [ "globals that are new only up to case", new_up_to_case ] ],
3069      [ "DeclareProperty",
3070        [ "new properties", new ],
3071        [ "redeclared properties", redeclared ],
3072        [ "globals that are new only up to case", new_up_to_case ] ],
3073      [ "DeclareCategory",
3074        [ "new categories", new ],
3075        [ "redeclared categories", redeclared ],
3076        [ "globals that are new only up to case", new_up_to_case ] ],
3077      [ "DeclareRepresentation",
3078        [ "new representations", new ],
3079        [ "redeclared representations", redeclared ],
3080        [ "globals that are new only up to case", new_up_to_case ] ],
3081      [ "DeclareFilter",
3082        [ "new plain filters", new ],
3083        [ "redeclared plain filters", redeclared ],
3084        [ "globals that are new only up to case", new_up_to_case ] ],
3085      [ "InstallMethod",
3086        [ "new methods", newmethod ] ],
3087      [ "InstallOtherMethod",
3088        [ "new other methods", newmethod ] ],
3089      [ "DeclareSynonymAttr",
3090        [ "new synonyms of attributes", new ],
3091        [ "globals that are new only up to case", new_up_to_case ] ],
3092      [ "DeclareSynonym",
3093        [ "new synonyms", new ],
3094        [ "globals that are new only up to case", new_up_to_case ] ],
3095      [ "DeclareInfoClass",
3096        [ "new info classes", new ] ],
3097      [ "KeyDependentOperation",
3098        [ "KeyDependentOperation", key_dependent_operation ] ],
3099      ];
3100
3101    # Save the relevant global variables, and replace them.
3102    GAPInfo.data:= rec( userGVars:= NamesUserGVars(),
3103                        varsThisPackage:= [],
3104                        revision_components:= [],
3105                        pkgpath:= test,
3106                        pkgname:= pkgname );
3107
3108    GAPInfo.data.lowercase_vars:= List( Union( NamesSystemGVars(),
3109        GAPInfo.data.userGVars ), LowercaseString );
3110
3111    localBindGlobal:= BindGlobal;
3112
3113    for rule in rules do
3114      GAPInfo.data.( rule[1] ):= [ ValueGlobal( rule[1] ), [] ];
3115      MakeReadWriteGlobal( rule[1] );
3116      UnbindGlobal( rule[1] );
3117      localBindGlobal( rule[1], EvalString( Concatenation(
3118          "function( arg ) ",
3119          "local infile, path; ",
3120          "infile:= INPUT_FILENAME(); ",
3121          "path:= GAPInfo.data.pkgpath; ",
3122          "if Length( path ) <= Length( infile ) and ",
3123          "   infile{ [ 1 .. Length( path ) ] } = path then ",
3124          "  Add( GAPInfo.data.( \"", rule[1], "\" )[2], ",
3125          "       [ StructuralCopy( arg ), infile, INPUT_LINENUMBER() ] ); ",
3126          "fi; ",
3127          "CallFuncList( GAPInfo.data.( \"", rule[1], "\" )[1], arg ); ",
3128          "end" ) ) );
3129    od;
3130
3131    # Redirect `ReadPackage'.
3132    GAPInfo.data.ReadPackage:= ReadPackage;
3133    MakeReadWriteGlobal( "ReadPackage" );
3134    UnbindGlobal( "ReadPackage" );
3135    localBindGlobal( "ReadPackage", EvalString( Concatenation(
3136        "function( arg ) ",
3137        "local pos, pkgname, before, cbefore, res, after, cafter; ",
3138        "if Length( arg ) = 1 then ",
3139        "  pos:= Position( arg[1], '/' ); ",
3140        "  pkgname:= LowercaseString( arg[1]{[ 1 .. pos - 1 ]} ); ",
3141        "elif Length( arg ) = 2 then ",
3142        "  pkgname:= LowercaseString( arg[1] ); ",
3143        "else ",
3144        "  pkgname:= fail; ",
3145        "fi; ",
3146        "if pkgname = GAPInfo.data.pkgname then ",
3147        "  before:= NamesUserGVars(); ",
3148        "  if IsBoundGlobal( \"Revision\" ) then ",
3149        "    cbefore:= RecNames( ValueGlobal( \"Revision\" ) ); ",
3150        "  fi; ",
3151        "fi; ",
3152        "res:= CallFuncList( GAPInfo.data.ReadPackage, arg ); ",
3153        "if pkgname = GAPInfo.data.pkgname then ",
3154        "  after:= NamesUserGVars(); ",
3155        "  UniteSet( GAPInfo.data.varsThisPackage, ",
3156        "    Filtered( Difference( after, before ), IsBoundGlobal ) ); ",
3157        "  if IsBoundGlobal( \"Revision\" ) then ",
3158        "    cafter:= RecNames( ValueGlobal( \"Revision\" ) ); ",
3159        "    UniteSet( GAPInfo.data.revision_components, ",
3160        "      Difference( cafter, cbefore ) ); ",
3161        "  fi; ",
3162        "fi; ",
3163        "return res; ",
3164        "end" ) ) );
3165
3166    # Load the package `pkgname'.
3167    loaded:= LoadPackage( pkgname );
3168
3169    # Put the original global variables back.
3170    for rule in rules do
3171      MakeReadWriteGlobal( rule[1] );
3172      UnbindGlobal( rule[1] );
3173      localBindGlobal( rule[1], GAPInfo.data.( rule[1] )[1] );
3174    od;
3175    MakeReadWriteGlobal( "ReadPackage" );
3176    UnbindGlobal( "ReadPackage" );
3177    localBindGlobal( "ReadPackage", GAPInfo.data.ReadPackage );
3178
3179    if not loaded then
3180      Print( "#E  the package `", pkgname, "' could not be loaded\n" );
3181      return [];
3182    fi;
3183
3184    # Functions are printed together with their argument lists.
3185    args:= function( func )
3186      local num, nam, str;
3187
3188      if not IsFunction( func ) then
3189        return "";
3190      fi;
3191      num:= NumberArgumentsFunction( func );
3192      nam:= NamesLocalVariablesFunction( func );
3193      if num = -1 then
3194        str:= "arg";
3195      elif nam = fail then
3196        str:= "...";
3197      else
3198        str:= JoinStringsWithSeparator( nam{ [ 1 .. num ] }, ", " );
3199      fi;
3200      return Concatenation( "( ", str, " )" );
3201    end;
3202
3203    # Mark undocumented globals with an asterisk.
3204    docmark:= function( nam )
3205      if not ( IsBoundGlobal( nam ) and IsDocumentedWord( nam ) ) then
3206        return "*";
3207      else
3208        return "";
3209      fi;
3210    end;
3211
3212    # Prepare the output.
3213    done:= [];
3214    result:= [];
3215    rules:= Filtered( rules, x -> x[1] <> "KeyDependentOperation" );
3216    for rule in rules do
3217      for subrule in rule{ [ 2 .. Length( rule ) ] } do
3218        added:= [];
3219        for entry in Filtered( List( GAPInfo.data.( rule[1] )[2],
3220                                     x -> subrule[2]( x ) ),
3221                               x -> x <> fail ) do
3222          if Length( entry ) = 5 then
3223            Add( added, [ [ entry[1], args( entry[2] ),
3224                            docmark( entry[1] ), entry[5] ],
3225                          [ entry[3], entry[4] ] ] );
3226          else
3227            Add( added, [ [ entry[1], args( entry[2] ),
3228                            docmark( entry[1] ) ],
3229                          [ entry[3], entry[4] ] ] );
3230          fi;
3231        od;
3232        if not IsEmpty( added ) then
3233          prev:= First( result, x -> x[1] = subrule[1] );
3234          if prev = fail then
3235            Add( result, [ subrule[1], added ] );
3236          else
3237            Append( prev[2], added );
3238          fi;
3239          UniteSet( done, List( added, x -> x[1][1] ) );
3240        fi;
3241      od;
3242    od;
3243    for subresult in result do
3244      Sort( subresult[2] );
3245    od;
3246
3247    # Mention the remaining new globals.
3248    isrelevant:= function( name )
3249      local name2, attr;
3250
3251      # Omit variables that are not bound anymore.
3252      # (We have collected the new variables file by file, and it may happen
3253      # that some of them become unbound in the meantime.)
3254      if not IsBoundGlobal( name ) then
3255        return false;
3256      fi;
3257
3258      # Omit `Set<attr>' and `Has<attr>' type variables.
3259      if 3 < Length( name ) and name{ [ 1 .. 3 ] } in [ "Has", "Set" ] then
3260        name2:= name{ [ 4 .. Length( name ) ] };
3261        if not IsBoundGlobal( name2 ) then
3262          return true;
3263        fi;
3264        attr:= ValueGlobal( name2 );
3265        if ForAny( ATTRIBUTES, entry -> IsIdenticalObj( attr, entry[3] ) ) then
3266          return false;
3267        fi;
3268      fi;
3269
3270      # Omit operation and attribute created by `KeyDependentOperation'.
3271      if 9 < Length( name  ) and name{ [ 1 .. 8 ] } = "Computed"
3272         and name[ Length( name ) ] = 's'
3273         and IsBoundGlobal( name{ [ 9 .. Length( name ) - 1 ] } )
3274         and ForAny( GAPInfo.data.KeyDependentOperation[2],
3275                     x -> x[1][1] = name{ [ 9 .. Length( name ) - 1 ] } ) then
3276        return false;
3277      fi;
3278      if 3 < Length( name  ) and name{ Length( name ) - [1,0] } = "Op"
3279         and IsBoundGlobal( name{ [ 1 .. Length( name ) - 2 ] } )
3280         and ForAny( GAPInfo.data.KeyDependentOperation[2],
3281                     x -> x[1][1] = name{ [ 1 .. Length( name ) - 2 ] } ) then
3282        return false;
3283      fi;
3284
3285      return true;
3286    end;
3287
3288    added:= Filtered( Difference( GAPInfo.data.varsThisPackage, done ),
3289                      isrelevant );
3290
3291    # Distinguish write protected variables from others.
3292    guesssource:= function( nam )
3293      local val;
3294
3295      val:= ValueGlobal( nam );
3296      if IsFunction( val ) then
3297        return [ FilenameFunc( val ), StartlineFunc( val ) ];
3298      else
3299        return [ fail, fail ];
3300      fi;
3301    end;
3302
3303    protected:= Filtered( added, IsReadOnlyGVar );
3304    if not IsEmpty( protected ) then
3305      Add( result, [ "other new globals (write protected)",
3306                     List( SortedList( protected ),
3307                           nam -> [ [ nam, args( ValueGlobal( nam ) ),
3308                                      docmark( nam ) ],
3309                                    guesssource( nam ) ] ) ] );
3310    fi;
3311    added:= Difference( added, protected );
3312    if not IsEmpty( added ) then
3313      Add( result, [ "other new globals (not write protected)",
3314                     List( SortedList( added ),
3315                           nam -> [ [ nam, args( ValueGlobal( nam ) ),
3316                                      docmark( nam ) ],
3317                                    guesssource( nam ) ] ) ] );
3318    fi;
3319
3320    # Have new components been added to `Revision'?
3321    if not IsEmpty( GAPInfo.data.revision_components ) then
3322      Add( result, [ "new components of the outdated 'Revision' record",
3323                     List( GAPInfo.data.revision_components,
3324                           x -> [ [ x, "", "" ], [ fail, fail ] ] ) ] );
3325    fi;
3326
3327    # Delete the auxiliary component from `GAPInfo'.
3328    Unbind( GAPInfo.data );
3329
3330    # Store the data.
3331    GAPInfo.PackageVariablesInfo.( cache ):= result;
3332    if version = "" then
3333      Append( cache, InstalledPackageVersion( pkgname ) );
3334      GAPInfo.PackageVariablesInfo.( cache ):= result;
3335    fi;
3336
3337    return result;
3338    end );
3339
3340Unbind( NamesSystemGVars );
3341Unbind( NamesUserGVars );
3342
3343
3344#############################################################################
3345##
3346#F  ShowPackageVariables( <pkgname>[, <version>][, <arec>] )
3347##
3348InstallGlobalFunction( ShowPackageVariables, function( arg )
3349    local version, arec, pkgname, info, show, documented, undocumented,
3350          private, result, len, format, entry, first, subentry, str;
3351
3352    # Get and check the arguments.
3353    version:= "";
3354    arec:= rec();
3355    if   Length( arg ) = 1 and IsString( arg[1] ) then
3356      pkgname:= LowercaseString( arg[1] );
3357    elif Length( arg ) = 2 and IsString( arg[1] ) and IsString( arg[2] ) then
3358      pkgname:= LowercaseString( arg[1] );
3359      version:= arg[2];
3360    elif Length( arg ) = 2 and IsString( arg[1] ) and IsRecord( arg[2] ) then
3361      pkgname:= LowercaseString( arg[1] );
3362      arec:= arg[2];
3363    elif Length( arg ) = 3 and IsString( arg[1] ) and IsString( arg[2] )
3364                           and IsRecord( arg[3] ) then
3365      pkgname:= LowercaseString( arg[1] );
3366      version:= arg[2];
3367      arec:= arg[3];
3368    else
3369      Error( "usage: ShowPackageVariables( <pkgname>[, <version>]",
3370             "[, <arec>] )" );
3371    fi;
3372
3373    # Compute the data.
3374    info:= PackageVariablesInfo( pkgname, version );
3375
3376    # Evaluate the optional record.
3377    if IsBound( arec.show ) and IsList( arec.show ) then
3378      show:= arec.show;
3379    else
3380      show:= List( info, entry -> entry[1] );
3381    fi;
3382    documented:= not IsBound( arec.showDocumented )
3383                 or arec.showDocumented <> false;
3384    undocumented:= not IsBound( arec.showUndocumented )
3385                   or arec.showUndocumented <> false;
3386    private:= not IsBound( arec.showPrivate )
3387              or arec.showPrivate <> false;
3388
3389    # Render the relevant data.
3390    result:= "";
3391    len:= SizeScreen()[1] - 2;
3392    if IsBoundGlobal( "FormatParagraph" ) then
3393      format:= ValueGlobal( "FormatParagraph" );
3394    else
3395      format:= function( arg ) return Concatenation( arg[1], "\n" ); end;
3396    fi;
3397    for entry in info do
3398      if entry[1] in show then
3399        first:= true;
3400        for subentry in entry[2] do
3401          if ( ( documented and subentry[1][3] = "" ) or
3402               ( undocumented and subentry[1][3] = "*" ) ) and
3403             ( private or not '@' in subentry[1][1] ) then
3404            if first then
3405              Append( result, entry[1] );
3406              Append( result, ":\n" );
3407              first:= false;
3408            fi;
3409            Append( result, "  " );
3410            for str in subentry[1]{ [ 1 .. 3 ] } do
3411              Append( result, str );
3412            od;
3413            Append( result, "\n" );
3414            if Length( subentry[1] ) = 4 and not IsEmpty( subentry[1][4] ) then
3415              Append( result,
3416                      format( subentry[1][4], len, "left", [ "    ", "" ] ) );
3417            fi;
3418          fi;
3419        od;
3420        if not first then
3421          Append( result, "\n" );
3422        fi;
3423      fi;
3424    od;
3425
3426    # Show the relevant data.
3427    if IsBound( arec.Display ) then
3428      arec.Display( result );
3429    else
3430      Print( result );
3431    fi;
3432    end );
3433