Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/GAP/lib/   (Algebra von RWTH Aachen Version 4.15.1©)  Datei vom 18.9.2025 mit Größe 126 kB image not shown  

SSL package.gi   Sprache: unbekannt

 
#############################################################################
##
##  This file is part of GAP, a system for computational discrete algebra.
##  This file's authors include Frank Celler, Alexander Hulpke.
##
##  Copyright of GAP belongs to its developers, whose names are too numerous
##  to list here. Please refer to the COPYRIGHT file for details.
##
##  SPDX-License-Identifier: GPL-2.0-or-later
##
##  This file contains support for ⪆ packages.
##


# recode string to GAPInfo.TermEncoding, assuming input is UTF-8 or latin1
# (if useful this may become documented for general use)
BindGlobal( "RecodeForCurrentTerminal", function( str )
    local fun, u;
    if IsBoundGlobal( "Unicode" ) and IsBoundGlobal( "Encode" ) then
      # The GAPDoc package is completely loaded.
      fun:= ValueGlobal( "Unicode" );
      u:= fun( str, "UTF-8" );
      if u = fail then
        u:= fun( str, "ISO-8859-1");
      fi;
      if GAPInfo.TermEncoding <> "UTF-8" then
        fun:= ValueGlobal( "SimplifiedUnicodeString" );
        u:= fun( u, GAPInfo.TermEncoding );
      fi;
      fun:= ValueGlobal( "Encode" );
      u:= fun( u, GAPInfo.TermEncoding );
      return u;
    else
      # GAPDoc is not yet available, do nothing in this case.
      return str;
    fi;
  end );

#############################################################################
##
#F  CompareVersionNumbers( <supplied>, <required>[, "equal"] )
##
InstallGlobalFunction( CompareVersionNumbers, function( arg )
    local s, r, inequal, i, j, a, b;

    s:= arg[1];
    r:= arg[2];
    inequal:= not ( Length( arg ) = 3 and arg[3] = "equal" );

    # Deal with the case of a `dev' version.
    if   2 < Length( s )
       and s{ [ Length( s ) - 2 .. Length( s ) ] } = "dev" then
      return inequal or ( Length(r)>2 and r{[Length(r)-2..Length(r)]}="dev" );
    elif 2 < Length( r )
       and r{ [ Length( r ) - 2 .. Length( r ) ] } = "dev" then
      return false;
    fi;

    while 0 < Length( s ) or 0 < Length( r ) do

      # Remove leading non-digit characters.
      i:= 1;
      while i <= Length( s ) and not IsDigitChar( s[i] ) do
        i:= i+1;
      od;
      s:= s{ [ i .. Length( s ) ] };
      j:= 1;
      while j <= Length( r ) and not IsDigitChar( r[j] ) do
        j:= j+1;
      od;
      r:= r{ [ j .. Length( r ) ] };

      # If one of the two strings is empty then we are done.
      if   Length( s ) = 0 then
        return Length( r ) = 0;
      elif Length( r ) = 0 then
        return inequal;
      fi;

      # Compare the next portion of digit characters.
      i:= 1;
      while i <= Length( s ) and IsDigitChar( s[i] ) do
        i:= i+1;
      od;
      a:= Int( s{ [ 1 .. i-1 ] } );
      j:= 1;
      while j <= Length( r ) and IsDigitChar( r[j] ) do
        j:= j+1;
      od;
      b:= Int( r{ [ 1 .. j-1 ] } );
      if   a < b then
        return false;
      elif b < a then
        return inequal;
      fi;
      s:= s{ [ i .. Length( s ) ] };
      r:= r{ [ j .. Length( r ) ] };

    od;

    # The two remaining strings are empty.
    return true;
end );


#############################################################################
##
#F  PackageInfo( <pkgname> )
##
InstallGlobalFunction( PackageInfo, function( pkgname )
    pkgname:= LowercaseString( pkgname );
    if not IsBound( GAPInfo.PackagesInfo.( pkgname ) ) then
      return [];
    else
      return GAPInfo.PackagesInfo.( pkgname );
    fi;
    end );


#############################################################################
##
#F  RECORDS_FILE( <name> )
##
InstallGlobalFunction( RECORDS_FILE, function( name )
    local str, rows, recs, pos, r;

    str:= StringFile( name );
    if str = fail then
      return [];
    fi;
    rows:= SplitString( str, "", "\n" );
    recs:= [];
    for r in rows do
      # remove comments starting with `#'
      pos:= Position( r, '#' );
      if pos <> fail then
        r:= r{ [ 1 .. pos-1 ] };
      fi;
      Append( recs, SplitString( r, "", " \n\t\r" ) );
    od;
    return List( recs, LowercaseString );
    end );


#############################################################################
##
#F  SetPackageInfo( <record> )
##
InstallGlobalFunction( SetPackageInfo, function( record )
    local rnam, info;
    if IsHPCGAP then
        info := rec();
        for rnam in REC_NAMES(record) do
          info.(rnam) := Immutable(record.(rnam));
        od;
        record := info;
    fi;
    GAPInfo.PackageInfoCurrent:= record;
    end );


#############################################################################
##
#F  FindPackageInfosInSubdirectories( pkgdir, name )
##
##  Finds all PackageInfos in subdirectories of directory name in
##  directory pkgdir, return a list of their paths.
##
BindGlobal( "FindPackageInfosInSubdirectories", function( pkgdir, name )
    local pkgpath, file, files, subdir;
    pkgpath:= Filename( [ pkgdir ], name );
    # This can be 'fail' if 'name' is a void link.
    if pkgpath = fail then
      return [];
    fi;

    if not IsDirectoryPath( pkgpath ) then
      return [];
    fi;
    if name in [ ".", ".." ] then
      return [];
    fi;

    file:= Filename( [ pkgdir ],
                      Concatenation( name, "/PackageInfo.g" ) );
    if file = fail then
      files := [];
      # Perhaps some subdirectories contain `PackageInfo.g' files.
      for subdir in Set( DirectoryContents( pkgpath ) ) do
        if not subdir in [ ".", ".." ] then
          pkgpath:= Filename( [ pkgdir ],
                              Concatenation( name, "/", subdir ) );
          if pkgpath <> fail and IsDirectoryPath( pkgpath )
                              and not subdir in [ ".", ".." ] then
            file:= Filename( [ pkgdir ],
                Concatenation( name, "/", subdir, "/PackageInfo.g" ) );
            if file <> fail then
              Add( files,
                    [ file, Concatenation( name, "/", subdir ) ] );
            fi;
          fi;
        fi;
      od;
    else
      files:= [ [ file, name ] ];
    fi;
    return files;
end );


#############################################################################
##
#F  AddPackageInfo( files )
##
BindGlobal( "AddPackageInfos", function( files, pkgdir, ignore )
    local file, record, pkgname, date, dd, mm;
    for file in files do
      # Read the `PackageInfo.g' file.
      Unbind( GAPInfo.PackageInfoCurrent );
      Read( file[1] );
      if IsBound( GAPInfo.PackageInfoCurrent ) then
        record:= GAPInfo.PackageInfoCurrent;
        Unbind( GAPInfo.PackageInfoCurrent );
        pkgname:= LowercaseString( record.PackageName );
        NormalizeWhitespace( pkgname );

        # Check whether GAP wants to reset loadability.
        if     IsBound( GAPInfo.PackagesRestrictions.( pkgname ) )
            and GAPInfo.PackagesRestrictions.( pkgname ).OnInitialization(
                    record ) = false then
          Add( GAPInfo.PackagesInfoRefuseLoad, record );
        elif pkgname in ignore then
          LogPackageLoadingMessage( PACKAGE_DEBUG,
              Concatenation( "ignore package ", record.PackageName,
              " (user preference PackagesToIgnore)" ), "GAP" );
        else
          record.InstallationPath:= Filename( [ pkgdir ], file[2] );
          # normalize to include trailing "/"
          record.InstallationPath:= Filename( [ Directory( record.InstallationPath ) ], "" );
          if not IsBound( record.PackageDoc ) then
            record.PackageDoc:= [];
          elif IsRecord( record.PackageDoc ) then
            record.PackageDoc:= [ record.PackageDoc ];
          fi;

          # Normalize the format of 'Date', i.e. if it is the format yyyy-mm-dd
          # then we change it to dd/mm/yyyy. When other tools have adapted to
          # the yyyy-mm-dd format we can normalize to that format and at some
          # point in the future get rid of this code.
          if Length(record.Date) = 10 and record.Date{[5,8]} = "--" then
            date := List( SplitString( record.Date, "-" ), Int);
            date := Permuted(date, (1,3)); # date = [dd,mm,yyyy]
            # generate the day and month strings
            # if the day has only one digit we have to add a 0
            if date[1] < 10 then
              dd := Concatenation("0", String(date[1]));
            else
              dd := String(date[1]);
            fi;
            # if the month has only one digit we have to add a 0
            if date[2] < 10 then
              mm := Concatenation("0", String(date[2]));
            else
              mm := String(date[2]);
            fi;
            record.Date := Concatenation(dd, "/", mm, "/", String(date[3]));
          fi;

          if IsHPCGAP then
            # FIXME: we make the package info record immutable, to
            # allow access from multiple threads; but that in turn
            # can break packages, which rely on their package info
            # record being readable (see issue #2568)
            MakeImmutable(record);
          fi;
          Add( GAPInfo.PackagesInfo, record );
        fi;
      fi;
    od;
end );

#############################################################################
##
#F  InitializePackagesInfoRecords()
##
##  In earlier versions, this function had an argument; now we ignore it.
##
InstallGlobalFunction( InitializePackagesInfoRecords, function( arg )
    local pkgdirs, pkgdir, pkgdirstrs, ignore, name, file, files, record, r;

    if IsBound( GAPInfo.PackagesInfoInitialized ) and
       GAPInfo.PackagesInfoInitialized = true then
      # This function has already been executed in this session.
      return;
    fi;

    GAPInfo.LoadPackageLevel:= 0;
    GAPInfo.PackagesInfo:= [];
    GAPInfo.PackagesInfoRefuseLoad:= [];

    LogPackageLoadingMessage( PACKAGE_DEBUG,
        "entering InitializePackagesInfoRecords", "GAP" );

    # the first time this is called, add the cmd line args to the list
    if IsEmpty(GAPInfo.PackageDirectories) then
        for pkgdirstrs in GAPInfo.CommandLineOptions.packagedirs do
            pkgdirs:= List( SplitString( pkgdirstrs, ";" ), Directory );
            for pkgdir in pkgdirs do
                if not pkgdir in GAPInfo.PackageDirectories then
                    Add( GAPInfo.PackageDirectories, pkgdir );
                fi;
            od;
        od;
    fi;
    # add any new pkg directories to the list
    pkgdirs:= DirectoriesLibrary( "pkg" );
    if pkgdirs <> fail then
        pkgdirs:= Filtered( pkgdirs, dir -> not dir in GAPInfo.PackageDirectories );
        if not IsEmpty(pkgdirs) then
            Append( GAPInfo.PackageDirectories, pkgdirs );
        fi;
    fi;

    if IsEmpty(GAPInfo.PackageDirectories) then
      LogPackageLoadingMessage( PACKAGE_DEBUG,
          "exit InitializePackagesInfoRecords (no pkg directories found)",
          "GAP" );
      GAPInfo.PackagesInfo:= AtomicRecord();
      return;
    fi;

    if IsBound( GAPInfo.ExcludeFromAutoload ) then
      # The function was called from `AutoloadPackages'.
      # Collect the `NOAUTO' information in a global list,
      # which will be used in `AutoloadPackages' for both packages to be
      # loaded according to the user preference "PackagesToLoad"
      # and suggested packages of needed packages.
      # The component `GAPInfo.ExcludeFromAutoload' will be unbound after the
      # call of `AutoloadPackages'.
      GAPInfo.ExcludeFromAutoload:= Set(
          UserPreference( "ExcludeFromAutoload" ), LowercaseString );
    fi;

    # Do not store information about packages in "PackagesToIgnore".
    ignore:= List( UserPreference( "PackagesToIgnore" ), LowercaseString );

    # Loop over the package directories,
    # remove the packages listed in `NOAUTO' files from GAP's suggested
    # packages, and unite the information for the directories.
    for pkgdir in GAPInfo.PackageDirectories do

      if IsBound( GAPInfo.ExcludeFromAutoload ) then
        UniteSet( GAPInfo.ExcludeFromAutoload,
                  List( RECORDS_FILE( Filename( pkgdir, "NOAUTO" ) ),
                        LowercaseString ) );
      fi;

      # pkgdir may be a package instead of a package directory
      file:= Filename( [ pkgdir ], "PackageInfo.g" );
      if file <> fail then
        AddPackageInfos( [ [ file, "" ] ], pkgdir, ignore );
      else
        # Loop over subdirectories of this package directory.
        for name in Set( DirectoryContents( pkgdir ) ) do

            ## Get all package dirs
            files := FindPackageInfosInSubdirectories( pkgdir, name );

            AddPackageInfos( files, pkgdir, ignore );

        od;
      fi;
    od;

    # Sort the available info records by their version numbers.
    # (Sort stably in order to make sure that an instance from the first
    # possible root path gets chosen if the same version of a package
    # is available in several root paths.
    # Note that 'CompareVersionNumbers' returns 'true'
    # if the two arguments are equal.)
    StableSortParallel( List( GAPInfo.PackagesInfo, r -> r.Version ),
                  GAPInfo.PackagesInfo,
                  { a, b } -> not CompareVersionNumbers( a, b, "equal" ) and CompareVersionNumbers( a, b ) );

    # Turn the lists into records.
    record:= rec();
    for r in GAPInfo.PackagesInfo do
      name:= LowercaseString( r.PackageName );
      if IsBound( record.( name ) ) then
        record.( name ) := Concatenation( record.( name ), [ r ] );
      else
        record.( name ):= [ r ];
      fi;
      if IsHPCGAP then
        # FIXME: we make the package info record immutable, to
        # allow access from multiple threads; but that in turn
        # can break packages, which rely on their package info
        # record being readable (see issue #2568)
        MakeImmutable( record.( name ) );
      fi;
    od;
    GAPInfo.PackagesInfo:= AtomicRecord(record);

    GAPInfo.PackagesInfoInitialized:= true;
    LogPackageLoadingMessage( PACKAGE_DEBUG,
        "return from InitializePackagesInfoRecords", "GAP" );
end );


#############################################################################
##
#F  LinearOrderByPartialWeakOrder( <pairs>, <weights> )
##
##  The algorithm works with a directed graph
##  whose vertices are subsets of the <M>c_i</M>
##  and whose edges represent the given partial order.
##  We start with one vertex for each <M>x_i</M> and each <M>y_i</M>
##  from the input list, and draw an edge from <M>x_i</M> to <M>y_i</M>.
##  Furthermore,
##  we need a queue <M>Q</M> of the smallest vertices found up to now,
##  and a stack <M>S</M> of the largest vertices found up to now;
##  both <M>Q</M> and <M>S</M> are empty at the beginning.
##  Now we add the vertices without predecessors to <M>Q</M> and remove the
##  edges from these vertices until no more such vertex is found.
##  Then we put the vertices without successors on <M>S</M> and remove the
##  edges to these vertices until no more such vertex is found.
##  If edges are left then each of them leads eventually into a cycle in the
##  graph; we find a cycle and amalgamate it into a new vertex.
##  Now we repeat the process until all edges have disappeared.
##  Finally, the concatenation of <M>Q</M> and <M>S</M> gives us the sets
##  <M>c_i</M>.
##
InstallGlobalFunction( LinearOrderByPartialWeakOrder,
    function( pairs, weights )
    local Q, S, Qw, Sw, F, pair, vx, vy, v, pos, candidates, minwght,
          smallest, s, maxwght, largest, p, cycle, next, new;

    # Initialize the queue and the stack.
    Q:= [];
    S:= [];
    Qw:= [];
    Sw:= [];

    # Create a list of vertices according to the input.
    F:= [];
    for pair in Set( pairs ) do
      if pair[1] <> pair[2] then
        vx:= First( F, r -> r.keys[1] = pair[1] );
        if vx = fail then
          vx:= rec( keys:= [ pair[1] ], succ:= [], pred:= [] );
          Add( F, vx );
        fi;
        vy:= First( F, r -> r.keys[1] = pair[2] );
        if vy = fail then
          vy:= rec( keys:= [ pair[2] ], succ:= [], pred:= [] );
          Add( F, vy );
        fi;
        Add( vx.succ, vy );
        Add( vy.pred, vx );
      fi;
    od;

    # Assign the weights.
    weights:= SortedList( weights );
    for v in F do
      pos:= PositionSorted( weights, v.keys );
      if pos <= Length( weights ) and weights[ pos ][1] = v.keys[1] then
        v.wght:= weights[ pos ][2];
      else
        v.wght:= 0;
      fi;
    od;

    # While F contains a vertex, ...
    while not IsEmpty( F ) do

      # ... find the vertices in F without predecessors and add them to Q,
      # remove the edges from these vertices,
      # and remove these vertices from F.
      candidates:= Filtered( F, v -> IsEmpty( v.pred ) );
      if not IsEmpty( candidates ) then
        minwght:= infinity;    # larger than all admissible weights
        for v in candidates do
          if v.wght < minwght then
            minwght:= v.wght;
            smallest:= [ v ];
          elif v.wght = minwght then
            Add( smallest, v );
          fi;
        od;
        for v in smallest do
          Add( Q, v.keys );
          Add( Qw, v.wght );
          for s in v.succ do
            s.pred:= Filtered( s.pred, x -> not IsIdenticalObj( v, x ) );
            if IsEmpty( s.pred )
               and ForAll( smallest, x -> not IsIdenticalObj( s, x ) ) then
              Add( smallest, s );
            fi;
          od;
          pos:= PositionProperty( F, x -> IsIdenticalObj( v, x ) );
          Unbind( F[ pos ] );
          F:= Compacted( F );
        od;
      fi;

      # Then find the vertices in F without successors and put them on S,
      # remove the edges to these vertices,
      # and remove these vertices from F.
      candidates:= Filtered( F, v -> IsEmpty( v.succ ) );
      if not IsEmpty( candidates ) then
        maxwght:= -1;    # smaller than all admissible weights
        for v in candidates do
          if v.wght > maxwght then
            maxwght:= v.wght;
            largest:= [ v ];
          elif v.wght = maxwght then
            Add( largest, v );
          fi;
        od;
        for v in largest do
          Add( S, v.keys );
          Add( Sw, v.wght );
          for p in v.pred do
            p.succ:= Filtered( p.succ, x -> not IsIdenticalObj( v, x ) );
            if IsEmpty( p.succ )
               and ForAll( largest, x -> not IsIdenticalObj( p, x ) ) then
              Add( largest, p );
            fi;
          od;
          pos:= PositionProperty( F, x -> IsIdenticalObj( v, x ) );
          Unbind( F[ pos ] );
          F:= Compacted( F );
        od;
      fi;

      if not IsEmpty( F ) then
        # Find a cycle in F.
        # (Note that now any vertex has a successor,
        # so we may start anywhere, and eventually get into a cycle.)
        cycle:= [];
        next:= F[1];
        repeat
          Add( cycle, next );
          next:= next.succ[1];
          pos:= PositionProperty( cycle, x -> IsIdenticalObj( x, next ) );
        until pos <> fail;
        cycle:= cycle{ [ pos .. Length( cycle ) ] };

        # Replace the set of vertices in the cycle by a new vertex,
        # replace all edges from/to a vertex outside the cycle
        # to/from a vertex in the cycle by edges to/from the new vertex.
        new:= rec( keys:= [], succ:= [], pred:= [],
                   wght:= Maximum( List( cycle, v -> v.wght ) ) );
        for v in cycle do
          UniteSet( new.keys, v.keys );
          for s in v.succ do
            if ForAll( cycle, w -> not IsIdenticalObj( s, w ) ) then
              if ForAll( new.succ, w -> not IsIdenticalObj( s, w ) ) then
                Add( new.succ, s );
              fi;
              pos:= PositionProperty( s.pred, w -> IsIdenticalObj( v, w ) );
              if ForAll( s.pred, w -> not IsIdenticalObj( new, w ) ) then
                s.pred[ pos ]:= new;
              else
                Unbind( s.pred[ pos ] );
                s.pred:= Compacted( s.pred );
              fi;
            fi;
          od;
          for p in v.pred do
            if ForAll( cycle, w -> not IsIdenticalObj( p, w ) ) then
              if ForAll( new.pred, w -> not IsIdenticalObj( p, w ) ) then
                Add( new.pred, p );
              fi;
              pos:= PositionProperty( p.succ, w -> IsIdenticalObj( v, w ) );
              if ForAll( p.succ, w -> not IsIdenticalObj( new, w ) ) then
                p.succ[ pos ]:= new;
              else
                Unbind( p.succ[ pos ] );
                p.succ:= Compacted( p.succ );
              fi;
            fi;
          od;
          pos:= PositionProperty( F, x -> IsIdenticalObj( v, x ) );
          Unbind( F[ pos ] );
          F:= Compacted( F );
        od;
        Add( F, new );
      fi;

    od;

    # Now the whole input is distributed to Q and S.
    return rec( cycles:= Concatenation( Q, Reversed( S ) ),
                weights:= Concatenation( Qw, Reversed( Sw ) ) );
    end );


#############################################################################
##
#I  InfoPackageLoading
##
##  (We cannot do this in `package.gd'.)
##
DeclareInfoClass( "InfoPackageLoading" );


#############################################################################
##
#F  LogPackageLoadingMessage( <severity>, <message>[, <name>] )
##
if not IsBound( TextAttr ) then
  TextAttr:= "dummy";
fi;
#T needed? (decl. of GAPDoc is loaded before)

InstallGlobalFunction( LogPackageLoadingMessage, function( severity, message, currpkg... )
    local i;

    if Length( currpkg ) = 1 then
      currpkg:= currpkg[1];
    elif Length( currpkg ) > 1 then
      Error("usage: LogPackageLoadingMessage( <severity>, <message>[, <name>] )");
    elif IsBound( GAPInfo.PackageCurrent ) then
      # This happens inside availability tests.
      currpkg:= GAPInfo.PackageCurrent.PackageName;
    else
      currpkg:= "(unknown package)";
    fi;
    if IsString( message ) then
      message:= [ message ];
    fi;
    if severity <= PACKAGE_WARNING
       and UserPreference("UseColorsInTerminal") = true
       and IsBound( TextAttr )
       and IsRecord( TextAttr ) then
      if severity = PACKAGE_ERROR then
        message:= List( message,
            msg -> Concatenation( TextAttr.1, msg, TextAttr.reset ) );
      else
        message:= List( message,
            msg -> Concatenation( TextAttr.4, msg, TextAttr.reset ) );
      fi;
    fi;
    Add( GAPInfo.PackageLoadingMessages, [ currpkg, severity, message ] );
    Info( InfoPackageLoading, severity, currpkg, ": ", message[1] );
    for i in [ 2 .. Length( message ) ] do
      Info( InfoPackageLoading, severity, List( currpkg, x -> ' ' ),
            "  ", message[i] );
    od;
    end );

if not IsReadOnlyGlobal( "TextAttr" ) then
  Unbind( TextAttr );
fi;


#############################################################################
##
#F  DisplayPackageLoadingLog( [<severity>] )
##
InstallGlobalFunction( DisplayPackageLoadingLog, function( arg )
    local severity, entry, message, i;

    if Length( arg ) = 0 then
      severity:= PACKAGE_WARNING;
    else
      severity:= arg[1];
    fi;

    for entry in GAPInfo.PackageLoadingMessages do
      if severity >= entry[2] then
        message:= entry[3];
        Info( InfoPackageLoading, 1, entry[1], ": ", message[1] );
        for i in [ 2 .. Length( message ) ] do
          Info( InfoPackageLoading, 1, List( entry[1], x -> ' ' ),
                "  ", message[i] );
        od;
      fi;
    od;
    end );


#############################################################################
##
#F  PackageAvailabilityInfo( <name>, <version>, <record>, <suggested>,
#F      <checkall> )
##
InstallGlobalFunction( PackageAvailabilityInfo,
    function( name, version, record, suggested, checkall )
    local comp, loadinfo, Name, equal, pair,
          currversion, inforec, skip, msg, dep, currloadinfo, GAPequal,
          record_local, wght, pos, needed, test, name2, testpair;

    # Initialize the dependency info.
    for comp in [ "AlreadyHandled", "Dependencies",
                  "InstallationPaths", "Weights" ] do
      if not IsBound( record.( comp ) ) then
        record.( comp ):= [];
      fi;
    od;
    if not IsBound( record.LoadInfo ) then
      record.LoadInfo:= rec();
    fi;

    loadinfo:= record.LoadInfo;
    if loadinfo = fail then
      # happens if one fetches the global option but it is not set
      loadinfo:= rec();
    fi;
    loadinfo.name:= name;
    loadinfo.versions:= [];
    loadinfo.comment:= "";

    Name:= name;
    name:= LowercaseString( name );
    equal:= "";
    if StartsWith( version, "=" ) then
      equal:= "equal";
    fi;

    if name = "gap" then
      # This case occurs if a package requires a particular GAP version.
      if CompareVersionNumbers( GAPInfo.Version, version, equal ) then
        return true;
      else
        Append( loadinfo.comment,
                Concatenation( "required GAP version ", version,
                    " is not compatible with the actual version" ) );
        return false;
      fi;
    fi;

    # If the package `name' is already loaded then compare the version
    # number of the loaded package with the required one.
    # (Note that at most one version of a package can be available.)
    if IsBound( GAPInfo.PackagesLoaded.( name ) ) then
      if CompareVersionNumbers( GAPInfo.PackagesLoaded.( name )[2],
                                version, equal ) then
        return true;
      else
        Append( loadinfo.comment,
                Concatenation( "package '", name,
                    "' is already loaded, required version ", version,
                    " is not compatible with the actual version" ) );
        return false;
      fi;
    fi;

    # If the function was called from `AutoloadPackages'
    # and if the package is listed among the packages to be excluded
    # from autoload then exit.
    if IsBound( GAPInfo.ExcludeFromAutoload )
       and name in GAPInfo.ExcludeFromAutoload then
      LogPackageLoadingMessage( PACKAGE_DEBUG,
          "package to be excluded from autoload, return 'false'", Name );
      Append( loadinfo.comment,
              Concatenation( "package '", name,
                  "' shall be excluded from autoload, return 'false'" ) );
      return false;
    fi;

    # Deal with the case that `name' is among the packages
    # from whose tests the current check for `name' arose.
    for pair in record.AlreadyHandled do
      if name = pair[1] then
        if CompareVersionNumbers( pair[2], version, equal ) then
          # The availability of the package will be decided on an outer level.
          return fail;
        else
          # The version assumed on an outer level does not fit.
          Append( loadinfo.comment,
                  Concatenation( "for package '", name,
                      "', version ", pair[2],
                      " is assumed on an outer level, ",
                      "but version ", version, " is required here" ) );
          return false;
        fi;
      fi;
    od;

    # In recursive calls, regard the current package as handled,
    # of course in the version in question.
    currversion:= [ name ];
    Add( record.AlreadyHandled, currversion );

    # Get the info records for the package `name',
    # and take the first record that satisfies the conditions.
    # (Note that they are ordered w.r.t. descending version numbers.)
    for inforec in PackageInfo( name ) do

      Name:= inforec.PackageName;
      skip:= false;
      currversion[2]:= inforec.Version;

      if IsBound( inforec.Dependencies ) then
        dep:= inforec.Dependencies;
      else
        dep:= rec();
      fi;

      currloadinfo:= rec( version:= inforec.Version,
                           comment:= "",
                           dependencies:= [] );
      Add( loadinfo.versions, currloadinfo );

      # Test whether this package version fits.
      msg:= [ Concatenation( "PackageAvailabilityInfo for version ",
                             inforec.Version ) ];
      if version <> "" then
        if not CompareVersionNumbers( inforec.Version, version, equal ) then
          # The severity of the log message must be less than `PACKAGE_INFO',
          # since we do not want to see the message when looking for reasons
          # why the current package cannot be loaded.
          LogPackageLoadingMessage( PACKAGE_DEBUG,
              [ Concatenation( "PackageAvailabilityInfo: version ",
                    inforec.Version, " does not fit" ),
                Concatenation( "(required: ", version, ")" ) ],
              inforec.PackageName );
          Append( currloadinfo.comment,
                  Concatenation( "version ", version, "is required, " ) );
          if not checkall then
            continue;
          fi;
          skip:= true;
        else
          Add( msg, Concatenation( "(required: ", version, ")" ) );
          LogPackageLoadingMessage( PACKAGE_INFO, msg,
              inforec.PackageName );
        fi;
      else
        LogPackageLoadingMessage( PACKAGE_INFO, msg,
            inforec.PackageName );
      fi;

      # Test whether the required GAP version fits.
      if IsBound( dep.GAP ) then
        GAPequal:= "";
        if StartsWith( dep.GAP, "=" ) then
          GAPequal:= "equal";
        fi;
        if not CompareVersionNumbers( GAPInfo.Version, dep.GAP, GAPequal ) then
          LogPackageLoadingMessage( PACKAGE_INFO,
              Concatenation( "PackageAvailabilityInfo: required GAP version (",
                  dep.GAP, ") does not fit",
              inforec.PackageName ) );
          Append( currloadinfo.comment,
                  Concatenation( "GAP version ", dep.GAP, " is required, " ) );
          if not checkall then
            continue;
          fi;
          skip:= true;
        fi;
      fi;

      # Test whether the availability test function fits.
      GAPInfo.PackageCurrent:= inforec;
      test:= (not IsBound(inforec.AvailabilityTest)) or (inforec.AvailabilityTest() = true);
      Unbind( GAPInfo.PackageCurrent );
      if test = true then
        LogPackageLoadingMessage( PACKAGE_DEBUG,
            Concatenation( "PackageAvailabilityInfo: the AvailabilityTest",
                " function returned 'true'" ),
            inforec.PackageName );
      else
        LogPackageLoadingMessage( PACKAGE_INFO,
            Concatenation( "PackageAvailabilityInfo: the AvailabilityTest",
                " function returned ", String( test ) ),
            inforec.PackageName );
        Append( currloadinfo.comment,
                Concatenation( "the AvailabilityTest function returned ",
                               String( test ), ", " ) );
        if not checkall then
          continue;
        fi;
        skip:= true;
      fi;

      # Locate the `init.g' file of the package.
      if Filename( [ Directory( inforec.InstallationPath ) ], "init.g" )
           = fail  then
        LogPackageLoadingMessage( PACKAGE_WARNING,
            Concatenation( "PackageAvailabilityInfo: cannot locate `",
              inforec.InstallationPath,
              "/init.g', please check the installation" ),
            inforec.PackageName );
        Append( currloadinfo.comment,
                Concatenation( "cannot locate '",
                               inforec.InstallationPath, "/init.g', " ) );
        if not checkall then
          continue;
        fi;
        skip:= true;
      fi;

      record_local:= StructuralCopy( record );

      wght:= 1;
      pos:= PositionProperty( record_local.Weights, pair -> pair[1] = name );
      if pos = fail then
        Add( record_local.Weights, [ name, wght ] );
      else
        record_local.Weights[ pos ][2]:= wght;
      fi;

      # Check the dependencies of this package version.
      needed:= [];
      if IsBound( dep.NeededOtherPackages ) then
        Append( needed, dep.NeededOtherPackages );
      fi;
      test:= true;
      if IsEmpty( needed ) then
        LogPackageLoadingMessage( PACKAGE_DEBUG,
            "PackageAvailabilityInfo: no needed packages",
            inforec.PackageName );
      else
        LogPackageLoadingMessage( PACKAGE_DEBUG, Concatenation(
            [ "PackageAvailabilityInfo: check needed packages" ],
            List( needed,
                  pair -> Concatenation( pair[1], " (", pair[2], ")" ) ) ),
            inforec.PackageName );
        for pair in needed do
          name2:= LowercaseString( pair[1] );
          Add( currloadinfo.dependencies,
               rec( name:= name2,
                    versions:= [],
                    comment:= "" ) );
          record_local.LoadInfo:= Last( currloadinfo.dependencies );
          testpair:= PackageAvailabilityInfo( name2, pair[2], record_local,
                         suggested, checkall );

          if testpair = false then
            # This dependency is not satisfied.
            test:= false;
            LogPackageLoadingMessage( PACKAGE_INFO,
                Concatenation( "PackageAvailabilityInfo: dependency '",
                    name2, "' is not satisfied" ), inforec.PackageName );
            if not checkall then
              # Skip the check of other dependencies.
              break;
            fi;
          elif testpair <> true then
            # The package `name2' is available but not yet loaded.
            Add( record_local.Dependencies, [ name2, name ] );
          fi;
        od;
        LogPackageLoadingMessage( PACKAGE_DEBUG,
            "PackageAvailabilityInfo: check of needed packages done",
            inforec.PackageName );
      fi;
      if test = false then
        # At least one package needed by this version is not available,
        if not checkall then
          continue;
        fi;
        skip:= true;
      fi;

      # All checks for this version have been performed.
      # Go to the next installed version if some check failed.
      if skip then
        continue;
      fi;

      # The version given by `inforec' will be taken.
      # Copy the information back to the argument record.
      record.InstallationPaths:= record_local.InstallationPaths;
      Add( record.InstallationPaths,
           [ name, [ inforec.InstallationPath, inforec.Version,
                     inforec.PackageName, false ] ] );
      record.Dependencies:= record_local.Dependencies;
      record.AlreadyHandled:= record_local.AlreadyHandled;
      record.Weights:= record_local.Weights;

      if suggested and IsBound( dep.SuggestedOtherPackages ) then
        # Collect info about suggested packages and their dependencies,
        # but without extending 'LoadInfo'.
        LogPackageLoadingMessage( PACKAGE_DEBUG, Concatenation(
            [ "PackageAvailabilityInfo: check suggested packages" ],
            List( dep.SuggestedOtherPackages,
                  pair -> Concatenation( pair[1], " (", pair[2], ")" ) ) ),
            inforec.PackageName );
        for pair in dep.SuggestedOtherPackages do
          name2:= LowercaseString( pair[1] );
          # Do not change the information collected up to now
          # until we are sure that we will really use the suggested package.
          record_local:= StructuralCopy( record );
          Unbind( record_local.LoadInfo );
          test:= PackageAvailabilityInfo( name2, pair[2], record_local,
                     suggested, checkall );
          if test <> true then
            Add( record_local.Dependencies, [ name2, name ] );
            if test <> false then
              record.InstallationPaths:= record_local.InstallationPaths;
              record.Dependencies:= record_local.Dependencies;
              record.AlreadyHandled:= record_local.AlreadyHandled;
              record.Weights:= record_local.Weights;
            fi;
          fi;
        od;
        LogPackageLoadingMessage( PACKAGE_DEBUG,
            "PackageAvailabilityInfo: check of suggested packages done",
            inforec.PackageName );
      fi;

      # Print a warning if the package should better be upgraded.
      if IsBound( GAPInfo.PackagesRestrictions.( name ) ) then
        GAPInfo.PackagesRestrictions.( name ).OnLoad( inforec );
      fi;
#T component name OnLoad:
#T shouldn't this be done only if the package is actually loaded?

      LogPackageLoadingMessage( PACKAGE_INFO,
          Concatenation( "PackageAvailabilityInfo: version ",
                         inforec.Version, " is available" ),
          inforec.PackageName );

      return inforec.InstallationPath;

    od;

    # No info record satisfies the requirements.
    if not IsBound( GAPInfo.PackagesInfo.( name ) ) then
      inforec:= First( GAPInfo.PackagesInfoRefuseLoad,
                       r -> LowercaseString( r.PackageName ) = name );
      if inforec <> fail then
        # Some versions are installed but all were refused.
        GAPInfo.PackagesRestrictions.( name ).OnLoad( inforec );
      fi;
    fi;

    LogPackageLoadingMessage( PACKAGE_INFO,
        Concatenation( "PackageAvailabilityInfo: ",
            "no installed version fits" ), Name );

    return false;
end );


#############################################################################
##
#F  TestPackageAvailability( <name>[, <version>][, <checkall>] )
##
InstallGlobalFunction( TestPackageAvailability, function( arg )
    local name, version, checkall, result;

    # Get the arguments.
    name:= LowercaseString( arg[1] );
    version:= "";
    checkall:= false;
    if Length( arg ) = 2 then
      if IsString( arg[2] ) then
        version:= arg[2];
      elif arg[2] = true then
        checkall:= true;
      fi;
    elif Length( arg ) = 3 then
      if IsString( arg[2] ) then
        version:= arg[2];
      fi;
      if arg[3] = true then
        checkall:= true;
      fi;
    fi;

    # Ignore suggested packages.
    result:= PackageAvailabilityInfo( name, version, rec(), false,
                                      checkall );

    if result = false then
      return fail;
    else
      return result;
    fi;
    end );


#############################################################################
##
#F  IsPackageLoaded( <name>[, <version>] )
##
InstallGlobalFunction( IsPackageLoaded, function( name, version... )
    local result;

    if Length(version) > 0 then
        version := version[1];
    fi;
    result := IsPackageMarkedForLoading( name, version );
    if result then
        # check if the package actually completed loading
        result := GAPInfo.PackagesLoaded.( LowercaseString( name ) )[4];
    fi;
    return result;
    end );


#############################################################################
##
#F  IsPackageMarkedForLoading( <name>, <version> )
##
InstallGlobalFunction( IsPackageMarkedForLoading, function( name, version )
    local equal;

    equal:= "";
    if 0 < Length( version ) and version[1] = '=' then
      equal:= "equal";
    fi;
    name:= LowercaseString( name );
    return IsBound( GAPInfo.PackagesLoaded.( name ) )
           and CompareVersionNumbers( GAPInfo.PackagesLoaded.( name )[2],
                   version, equal );
    end );


#############################################################################
##
#F  DefaultPackageBannerString( <inforec> )
##

DeclareUserPreference( rec(
  name:= "ShortBanners",
  description:= [
    "If this option is set to <K>true</K>, package banners printed during \
loading will only show the name, version and description of a package."
    ],
  default:= false,
  values:= [ true, false ],
  multi:= false,
  ) );

InstallGlobalFunction( DefaultPackageBannerString,
    function( inforec, useShortBanner... )
    local len, sep, i, str, authors, maintainers, contributors, printPersons;

    if Length( useShortBanner ) = 0 then
      useShortBanner := false;
    elif Length( useShortBanner ) = 1 then
      useShortBanner := useShortBanner[1];
    else
      Error( "DefaultPackageBannerString must be called with at most two arguments" );
    fi;

    # Start with a row of `-' signs.
    len:= SizeScreen()[1] - 3;
    if GAPInfo.TermEncoding = "UTF-8" then
      # The unicode character we use takes up 3 bytes in UTF-8 encoding,
      # hence we must adjust the length accordingly.
      sep:= "─";
      i:= 1;
      while 2 * i <= len do
        Append( sep, sep );
        i:= 2 * i;
      od;
      Append( sep, sep{ [ 1 .. 3 * ( len - i ) ] } );
    else
      sep:= ListWithIdenticalEntries( len, '-' );
    fi;
    Add( sep, '\n' );

    str:= "";

    # Add package name and version number.
    if IsBound( inforec.PackageName ) and IsBound( inforec.Version ) then
      Append( str, Concatenation(
              "Loading ", inforec.PackageName, " ", inforec.Version ) );
    fi;

    # Add the long title.
    if IsBound( inforec.PackageDoc ) and IsBound( inforec.PackageDoc[1] ) and
       IsBound( inforec.PackageDoc[1].LongTitle ) and
       not IsEmpty( inforec.PackageDoc[1].LongTitle ) then
      Append( str, Concatenation(
              " (", inforec.PackageDoc[1].LongTitle, ")" ) );
    fi;
    Add( str, '\n' );

    if not useShortBanner then
        # Add info about the authors and/or maintainers
        printPersons := function( role, persons )
          local fill, person;
          fill:= ListWithIdenticalEntries( Length(role), ' ' );
          Append( str, role );
          for i in [ 1 .. Length( persons ) ] do
            person:= persons[i];
            if IsBound( person.FirstNames ) then
              Append( str, person.FirstNames );
              Append( str, " " );
            fi;
            Append( str, person.LastName );
            if   IsBound( person.WWWHome ) then
              Append( str, Concatenation( " (", person.WWWHome, ")" ) );
            elif IsBound( person.Email ) then
              Append( str, Concatenation( " (", person.Email, ")" ) );
            fi;
            if   i = Length( persons ) then
              Append( str, ".\n" );
            elif i = Length( persons )-1 then
              if i = 1 then
                Append( str, " and\n" );
              else
                Append( str, ", and\n" );
              fi;
              Append( str, fill );
            else
              Append( str, ",\n" );
              Append( str, fill );
            fi;
          od;
        end;
        if IsBound( inforec.Persons ) then
          authors:= Filtered( inforec.Persons, x -> x.IsAuthor );
          if not IsEmpty( authors ) then
            printPersons( "by ", authors );
          fi;
          contributors:= Filtered( inforec.Persons, x -> not x.IsAuthor and not x.IsMaintainer );
          if not IsEmpty( contributors ) then
            Append( str, "with contributions by:\n");
            printPersons( "   ", contributors );
          fi;
          maintainers:= Filtered( inforec.Persons, x -> x.IsMaintainer );
          if not IsEmpty( maintainers ) and authors <> maintainers then
            Append( str, "maintained by:\n");
            printPersons( "   ", maintainers );
          fi;
        fi;

        # Add info about the home page of the package.
        if IsBound( inforec.PackageWWWHome ) then
          Append( str, "Homepage: " );
          Append( str, inforec.PackageWWWHome );
          Append( str, "\n" );
        fi;

        # Add info about the issue tracker of the package.
        if IsBound( inforec.IssueTrackerURL ) then
          Append( str, "Report issues at " );
          Append( str, inforec.IssueTrackerURL );
          Append( str, "\n" );
        fi;

        str := Concatenation(sep, str, sep);
    fi;

    # temporary hack, in some package names with umlauts are in HTML encoding
    str := RecodeForCurrentTerminal(str);
    str:= ReplacedString( str, "ä", RecodeForCurrentTerminal("ä") );
    str:= ReplacedString( str, "ö", RecodeForCurrentTerminal("ö") );
    str:= ReplacedString( str, "ü", RecodeForCurrentTerminal("ü") );

    return str;
    end );


#############################################################################
##
#F  DirectoriesPackagePrograms( <name> )
##
InstallGlobalFunction( DirectoriesPackagePrograms, function( name )
    local info, installationpath;

    # We are not allowed to call
    # `InstalledPackageVersion', `TestPackageAvailability' etc.
    name:= LowercaseString( name );
    info:= PackageInfo( name );
    if IsBound( GAPInfo.PackagesLoaded.( name ) ) then
      # The package is already loaded.
      installationpath:= GAPInfo.PackagesLoaded.( name )[1];
    elif IsBound( GAPInfo.PackageCurrent ) and
         LowercaseString( GAPInfo.PackageCurrent.PackageName ) = name then
      # The package in question is currently going to be loaded.
      installationpath:= GAPInfo.PackageCurrent.InstallationPath;
    elif 0 < Length( info ) then
      # Take the installed package with the highest version
      # that has been found first in the root paths.
      installationpath:= info[1].InstallationPath;
    else
      # This package is not known.
      return [];
    fi;
    return [ Directory( Concatenation( installationpath, "/bin/",
                            GAPInfo.Architecture, "/" ) ) ];
end );


#############################################################################
##
#F  DirectoriesPackageLibrary( <name>[, <path>] )
##
InstallGlobalFunction( DirectoriesPackageLibrary, function( arg )
    local name, path, info, installationpath, tmp;

    if IsEmpty(arg) or 2 < Length(arg) then
        Error( "usage: DirectoriesPackageLibrary( <name>[, <path>] )" );
    elif not ForAll(arg, IsString) then
        Error( "string argument(s) expected" );
    fi;

    name:= LowercaseString( arg[1] );
    if '\\' in name or ':' in name  then
        Error( "<name> must not contain '\\' or ':'" );
    elif 1 = Length(arg)  then
        path := "lib";
    else
        path := arg[2];
    fi;

    # We are not allowed to call
    # `InstalledPackageVersion', `TestPackageAvailability' etc.
    info:= PackageInfo( name );
    if IsBound( GAPInfo.PackagesLoaded.( name ) ) then
      # The package is already loaded.
      installationpath:= GAPInfo.PackagesLoaded.( name )[1];
    elif IsBound( GAPInfo.PackageCurrent ) and
         LowercaseString( GAPInfo.PackageCurrent.PackageName ) = name then
      # The package in question is currently going to be loaded.
      installationpath:= GAPInfo.PackageCurrent.InstallationPath;
    elif 0 < Length( info ) then
      # Take the installed package with the highest version
      # that has been found first in the root paths.
      installationpath:= info[1].InstallationPath;
    else
      # This package is not known.
      return [];
    fi;
    tmp:= Filename( Directory( installationpath ), path );
    if IsDirectoryPath( tmp ) = true then
      return [ Directory( tmp ) ];
    fi;
    return [];
end );


#############################################################################
##
#F  ReadPackage( [<name>, ]<file> )
#F  RereadPackage( [<name>, ]<file> )
##
BindGlobal( "_ReadPackage", function( arg, error )
    local pos, relpath, pkgname, namespace, filename;

    # Note that we cannot use `ReadAndCheckFunc' because this calls
    # `READ_GAP_ROOT', but here we have to read the file in one of those
    # directories where the package version resides that has been loaded
    # or (at least currently) would be loaded.
    if   Length( arg ) = 1 then
      # Guess the package name.
      pos:= Position( arg[1], '/' );
      if pos = fail then
        ErrorNoReturn(arg[1], " is not a filename in the form 'package/filepath'");
      fi;
      pkgname:= arg[1]{ [ 1 .. pos-1 ] };
      relpath:= arg[1]{ [ pos+1 .. Length( arg[1] ) ] };
    elif Length( arg ) = 2 then
      pkgname:= arg[1];
      relpath:= arg[2];
    else
      Error( "expected 1 or 2 arguments" );
    fi;
    pkgname:= LowercaseString( pkgname );
    namespace := GAPInfo.PackagesInfo.(pkgname)[1].PackageName;

    # Note that `DirectoriesPackageLibrary' finds the file relative to the
    # installation path of the info record chosen in `LoadPackage'.
    filename:= Filename( DirectoriesPackageLibrary( pkgname, "" ), relpath );
    if filename <> fail and IsReadableFile( filename ) then
      ENTER_NAMESPACE(namespace);
      Read( filename );
      LEAVE_NAMESPACE();
      return true;
    elif error then
      Info(InfoWarning, 1, "ReadPackage could not read <", pkgname, ">/", relpath, "\n");
    fi;
    return false;
    end );

InstallGlobalFunction( ReadPackage, function( arg )
    return _ReadPackage( arg, true );
    end );

InstallGlobalFunction( RereadPackage, function( arg )
    local res;

    MakeReadWriteGlobal( "REREADING" );
    REREADING:= true;
    MakeReadOnlyGlobal( "REREADING" );
    res:= _ReadPackage( arg, false );
    MakeReadWriteGlobal( "REREADING" );
    REREADING:= false;
    MakeReadOnlyGlobal( "REREADING" );
    return res;
    end );


#############################################################################
##
#F  LoadPackageDocumentation( <info> )
##
##  In versions before 4.5, a second argument was required.
##  For the sake of backwards compatibility, we do not forbid a second
##  argument, but we ignore it.
##  (In later versions, we may forbid the second argument.)
##
InstallGlobalFunction( LoadPackageDocumentation, function( arg )
    local info, short, pkgdoc, long, sixfile;

    info:= arg[1];

    # Load all books for the package.
    for pkgdoc in info.PackageDoc do
      # Fetch the names.
      if IsBound( pkgdoc.LongTitle ) then
        long:= pkgdoc.LongTitle;
      else
        long:= Concatenation( "GAP Package `", info.PackageName, "'" );
      fi;
      short:= pkgdoc.BookName;
      if not IsBound( GAPInfo.PackagesLoaded.( LowercaseString(
                          info.PackageName ) ) ) then
        short:= Concatenation( short, " (not loaded)" );
      fi;

      # Check that the `manual.six' file is available.
      sixfile:= Filename( [ Directory( info.InstallationPath ) ],
                          pkgdoc.SixFile );
      if sixfile = fail then
        LogPackageLoadingMessage( PACKAGE_INFO,
            Concatenation( [ "book `", pkgdoc.BookName,
                "': no manual index file `",
                pkgdoc.SixFile, "', ignored" ] ),
            info.PackageName );
      else
        # Finally notify the book via its directory.
#T Here we assume that this is the directory that contains also `manual.six'!
        HELP_ADD_BOOK( short, long,
            Directory( sixfile{ [ 1 .. Length( sixfile )-10 ] } ) );
      fi;
    od;
    end );

#############################################################################
##
#F  LoadPackage_ReadImplementationParts( <secondrun>, <banner> )
##
BindGlobal( "LoadPackage_ReadImplementationParts",
    function( secondrun, banner )
    local pair, info, bannerstring, pkgname, namespace;

    for pair in secondrun do
      namespace := pair[1].PackageName;
      pkgname := LowercaseString( namespace );
      if pair[2] <> fail then
        GAPInfo.PackageCurrent:= pair[1];
        LogPackageLoadingMessage( PACKAGE_DEBUG,
            "start reading file 'read.g'",
            namespace );
        ENTER_NAMESPACE(namespace);
        Read( pair[2] );
        LEAVE_NAMESPACE();
        Unbind( GAPInfo.PackageCurrent );
        LogPackageLoadingMessage( PACKAGE_DEBUG,
            "finish reading file 'read.g'",
            namespace );
      fi;
      # mark the package as completely loaded
      GAPInfo.PackagesLoaded.(pkgname)[4] := true;
      MakeImmutable( GAPInfo.PackagesLoaded.(pkgname) );
    od;

    # Show the banners.
    if banner then
      for pair in secondrun do
        info:= pair[1];

        # If the component `BannerString' is bound in `info' then we print
        # this string, otherwise we print the default banner string.
        if UserPreference( "ShortBanners" ) then
          bannerstring:= DefaultPackageBannerString( info, true );
        elif IsBound( info.BannerFunction ) then
          bannerstring:= RecodeForCurrentTerminal(info.BannerFunction(info));
        elif IsBound( info.BannerString ) then
          bannerstring:= RecodeForCurrentTerminal(info.BannerString);
        else
          bannerstring:= DefaultPackageBannerString( info );
        fi;

        # Suppress output formatting to avoid troubles with umlauts,
        # accents etc. in the banner.
        PrintWithoutFormatting( bannerstring );
      od;
    fi;
    end );


#############################################################################
##
#F  GetPackageNameForPrefix( <prefix> ) . . . . . . . .  show list of matches
#F                                                   or single match directly
##
##  Compute all names of installed packages that match the prefix <prefix>.
##  In case of a unique match return this match,
##  otherwise print an info message about the matches and return <prefix>.
##
##  This function is called by `LoadPackage'.
##
BindGlobal( "GetPackageNameForPrefix", function( prefix )
    local len, lowernames, name, allnames, indent, pos, sep;

    len:= Length( prefix );
    lowernames:= [];
    for name in Set( RecNames( GAPInfo.PackagesInfo ) ) do
      if Length( prefix ) <= Length( name ) and
         name{ [ 1 .. len ] } = prefix then
        Add( lowernames, name );
      fi;
    od;
    if IsEmpty( lowernames ) then
      # No package name matches.
      return prefix;
    fi;
    allnames:= List( lowernames,
                     nam -> GAPInfo.PackagesInfo.( nam )[1].PackageName );
    if Length( allnames ) = 1 then
      # There is one exact match.
      LogPackageLoadingMessage( PACKAGE_DEBUG, Concatenation(
          [ "replace prefix '", prefix, "' by the unique completion '",
            allnames[1], "'" ] ), allnames[1] );
      return lowernames[1];
    fi;

    # Several package names match.
    if 0 < InfoLevel( InfoPackageLoading ) then
      Print( "#I  Call 'LoadPackage' with one of the following strings:\n" );
      len:= SizeScreen()[1] - 6;
      indent:= "#I  ";
      Print( indent );
      pos:= Length( indent );
      sep:= "";
      for name in allnames do
        Print( sep );
        pos:= pos + Length( sep );
        if len < pos + Length( name ) then
          Print( "\n", indent );
          pos:= Length( indent );
        fi;
        Print( "\"", name, "\"" );
        pos:= pos + Length( name ) + 2;
        sep:= ", ";
      od;
      Print( ".\n" );
    fi;
    return prefix;
    end );


#############################################################################
##
#F  LoadPackage( <name>[, <version>][, <banner>] )
##
##  The global option <C>LoadInfo</C> (with value a mutable record)
##  can be used to collect information about the dependencies
##  which have been checked during a call of <Ref Func="LoadPackage"/>.
##  After the call, the record will contain data as described for
##  <C>PackageAvailabilityInfo</C>.
##
InstallGlobalFunction( LoadPackage, function( arg )
    local name, Name, version, banner, loadsuggested, msg, depinfo, path,
          pair, i, order, paths, cycle, secondrun, pkgname, pos, info,
          filename, entry, r, loadinfo;

    # Get the arguments.
    if Length( arg ) = 0 then
      name:= "";
    else
      name:= arg[1];
      if not IsString( name ) then
        Error( "<name> must be a string" );
      fi;
      name:= LowercaseString( name );
    fi;
    if not IsBound( GAPInfo.PackagesInfo.( name ) ) then
      name:= GetPackageNameForPrefix( name );
    fi;

    loadinfo:= ValueOption( "LoadInfo" );
    if loadinfo <> fail then
      if not ( IsRecord( loadinfo ) and IsMutable( loadinfo ) ) then
        Error( "option 'LoadInfo', if given, must be a mutable record" );
      fi;
    fi;

    # Return 'fail' if this package is not installed.
    if not IsBound( GAPInfo.PackagesInfo.( name ) ) then
      LogPackageLoadingMessage( PACKAGE_DEBUG,
          "no package with this name is installed, return 'fail'", name );
      if InfoLevel(InfoPackageLoading) < 4 then
        Info(InfoWarning,1, name, " package is not available. Check that the name is correct");
        Info(InfoWarning,1, "and it is present in one of the GAP root directories (see '??RootPaths')");
        if loadinfo <> fail then
          loadinfo.name:= name;
          loadinfo.comment:= "package is not listed in GAPInfo.PackagesInfo";
        fi;
      fi;
      return fail;
    fi;

    # The package is available, fetch the name for messages.
    Name:= GAPInfo.PackagesInfo.( name )[1].PackageName;
    version:= "";
    banner:= not GAPInfo.CommandLineOptions.q and
             not GAPInfo.CommandLineOptions.b;
    if 1 < Length( arg ) then
      if IsString( arg[2] ) then
        version:= arg[2];
        if 2 < Length( arg ) then
          banner:= banner and not ( arg[3] = false );
        fi;
      else
        banner:= banner and not ( arg[2] = false );
      fi;
    fi;
    loadsuggested:= ( ValueOption( "OnlyNeeded" ) <> true );

    # Print a warning if `LoadPackage' is called inside a
    # `LoadPackage' call.
    if not IsBound( GAPInfo.LoadPackageLevel ) then
      GAPInfo.LoadPackageLevel:= 0;
    fi;
    GAPInfo.LoadPackageLevel:= GAPInfo.LoadPackageLevel + 1;
    if GAPInfo.LoadPackageLevel <> 1 then
      if IsBound( GAPInfo.PackageCurrent ) then
        msg:= GAPInfo.PackageCurrent.PackageName;
      else
        msg:= "?";
      fi;
      LogPackageLoadingMessage( PACKAGE_WARNING,
          [ Concatenation( "Do not call `LoadPackage( \"", name,
                "\", ... )' in the package file" ),
            Concatenation( INPUT_FILENAME(), "," ),
            "use `IsPackageMarkedForLoading' instead" ], msg );
    fi;

    # Start logging.
    msg:= "entering LoadPackage ";
    if not loadsuggested then
      Append( msg, " (omitting suggested packages)" );
    fi;
    LogPackageLoadingMessage( PACKAGE_DEBUG, msg, Name );

    # Test whether the package is available,
    # and compute the dependency information.
    depinfo:= rec( LoadInfo:= loadinfo );
    path:= PackageAvailabilityInfo( name, version, depinfo, loadsuggested,
                                    false );
    if not IsString( path ) then
      if path = false then
        path:= fail;
      fi;
      # The result is either `true' (the package is already loaded)
      # or `fail' (the package cannot be loaded).
      if path = true then
        LogPackageLoadingMessage( PACKAGE_DEBUG,
            "return from LoadPackage, package was already loaded", Name );
      else
        LogPackageLoadingMessage( PACKAGE_DEBUG,
            "return from LoadPackage, package is not available", Name );
        if banner then
          if InfoLevel(InfoPackageLoading) < 4 then
            Info(InfoWarning,1, Name, " package is not available. To see further details, enter");
            Info(InfoWarning,1, "SetInfoLevel(InfoPackageLoading,4); and try to load the package again.");
          fi;
        fi;
      fi;
      GAPInfo.LoadPackageLevel:= GAPInfo.LoadPackageLevel - 1;
      return path;
    fi;

    # Suspend reordering of methods following InstallTrueMethod
    # because it would slow things down too much
    SuspendMethodReordering();

    # Compute the order in which the packages are loaded.
    # For each set of packages with cyclic dependencies,
    # we will first read all `init.g' files
    # and afterwards all `read.g' files.
    if IsEmpty( depinfo.Dependencies ) then
      order:= rec( cycles:= [ [ name ] ],
                   weights:= [ depinfo.Weights[1][2] ] );
    else
      order:= LinearOrderByPartialWeakOrder( depinfo.Dependencies,
                                             depinfo.Weights );
    fi;
    # paths:= TransposedMatMutable( depinfo.InstallationPaths );
    # (TransposedMatMutable is not yet available here ...)
    paths:= [ [], [] ];
    for pair in depinfo.InstallationPaths do
      Add( paths[1], pair[1] );
      Add( paths[2], pair[2] );
    od;
    SortParallel( paths[1], paths[2] );

    secondrun:= [];
    for i in [ 1 .. Length( order.cycles ) ] do
      cycle:= order.cycles[i];

      # First mark all packages in the current cycle as loaded,
      # in order to avoid that an occasional call of `LoadPackage'
      # inside the package code causes the files to be read more than once.
      for pkgname in cycle do
        pos:= PositionSorted( paths[1], pkgname );
        # the following entry is made immutable in LoadPackage_ReadImplementationParts
        GAPInfo.PackagesLoaded.( pkgname ):= paths[2][ pos ];
      od;

      if loadsuggested then
        msg:= "start loading needed/suggested/self packages";
      else
        msg:= "start loading needed/self packages";
      fi;
      LogPackageLoadingMessage( PACKAGE_DEBUG,
          Concatenation( [ msg ], cycle ),
          Name );

      for pkgname in cycle do
        pos:= PositionSorted( paths[1], pkgname );
        info:= First( PackageInfo( pkgname ),
                      r -> r.InstallationPath = paths[2][ pos ][1] );

        if not ValidatePackageInfo(info) then
           Print("#E Validation of package ", pkgname, " from ", info.InstallationPath, " failed\n");
        fi;

        # Notify the documentation (for the available version).
        LoadPackageDocumentation( info );

        # Notify extensions provided by the package.
        if IsBound( info.Extensions ) then
          for entry in info.Extensions do
            LogPackageLoadingMessage( PACKAGE_DEBUG,
                Concatenation( "notify extension ", entry.filename ),
                pkgname );
            r:= ShallowCopy( entry );
            r.providedby:= pkgname;
            Add( GAPInfo.PackageExtensionsPending, Immutable( r ) );
          od;
        fi;

        # Read the `init.g' files.
        LogPackageLoadingMessage( PACKAGE_DEBUG,
            "start reading file 'init.g'",
            info.PackageName );
        GAPInfo.PackageCurrent:= info;
        ReadPackage( pkgname, "init.g" );
        Unbind( GAPInfo.PackageCurrent );
        LogPackageLoadingMessage( PACKAGE_DEBUG,
            "finish reading file 'init.g'",
            info.PackageName );

        filename:= Filename( [ Directory( info.InstallationPath ) ],
                             "read.g" );
        Add( secondrun, [ info, filename ] );
      od;

      # Read the `read.g' files collected up to now.
      # Afterwards show the banners.
      # (We have delayed this until now because it uses functionality
      # from the package GAPDoc.)
      # Note that no banners are printed during autoloading.
      LoadPackage_ReadImplementationParts( secondrun, banner );
      secondrun:= [];

    od;

    # Load those package extensions whose condition is satisfied.
    for i in [ 1 .. Length( GAPInfo.PackageExtensionsPending ) ] do
      entry:= GAPInfo.PackageExtensionsPending[i];
      if ForAll( entry.needed, l -> IsPackageLoaded( l[1], l[2] ) ) then
        ReadPackage( entry.providedby, entry.filename );
        Add( GAPInfo.PackageExtensionsLoaded, entry );
        Unbind( GAPInfo.PackageExtensionsPending[i] );
        LogPackageLoadingMessage( PACKAGE_DEBUG,
            Concatenation( "load extension ", entry.filename ),
            entry.providedby );
      fi;
    od;
    GAPInfo.PackageExtensionsPending:= Compacted( GAPInfo.PackageExtensionsPending );

    LogPackageLoadingMessage( PACKAGE_DEBUG, "return from LoadPackage",
        Name );
    GAPInfo.LoadPackageLevel:= GAPInfo.LoadPackageLevel - 1;

    ResumeMethodReordering();
    return true;
    end );


#############################################################################
##
#F  LoadAllPackages()
##
InstallGlobalFunction( LoadAllPackages, function()
    SuspendMethodReordering();
    if ValueOption( "reversed" ) = true then
        List( Reversed( RecNames( GAPInfo.PackagesInfo ) ), LoadPackage );
    else
        List( RecNames( GAPInfo.PackagesInfo ), LoadPackage );
    fi;
    ResumeMethodReordering();
    end );


#############################################################################
##
#F  SetPackagePath( <pkgname>, <pkgpath> )
##
InstallGlobalFunction( SetPackagePath, function( pkgname, pkgpath )
    local pkgdir, file, record;

    InitializePackagesInfoRecords();
    pkgname:= LowercaseString( pkgname );
    NormalizeWhitespace( pkgname );
    if IsBound( GAPInfo.PackagesLoaded.( pkgname ) ) then
      # compare using `Directory` to expand "~" and add trailing "/"
      if Directory( GAPInfo.PackagesLoaded.( pkgname )[1] ) = Directory( pkgpath ) then
        return;
      fi;
      Error( "another version of package ", pkgname, " is already loaded" );
    fi;

    pkgdir:= Directory( pkgpath );
    file:= Filename( [ pkgdir ], "PackageInfo.g" );
    if file = fail then
      return;
    fi;
    Unbind( GAPInfo.PackageInfoCurrent );
    Read( file );
    record:= GAPInfo.PackageInfoCurrent;
    Unbind( GAPInfo.PackageInfoCurrent );
    if pkgname <> NormalizedWhitespace( LowercaseString(
                      record.PackageName ) ) then
      Error( "found package ", record.PackageName, " not ", pkgname,
             " in ", pkgpath );
    fi;
    if IsBound( GAPInfo.PackagesRestrictions.( pkgname ) )
       and GAPInfo.PackagesRestrictions.( pkgname ).OnInitialization(
               record ) = false  then
      Add( GAPInfo.PackagesInfoRefuseLoad, record );
    else
      record.InstallationPath:= Filename( [ pkgdir ], "" );
      if not IsBound( record.PackageDoc ) then
        record.PackageDoc:= [];
--> --------------------

--> maximum size reached

--> --------------------

[ Verzeichnis aufwärts0.59unsichere Verbindung  Übersetzung europäischer Sprachen durch Browser  ]