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 &GAP; 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, "ä", RecodeForCurrentTerminal("ä") ); 1181 str:= ReplacedString( str, "ö", RecodeForCurrentTerminal("ö") ); 1182 str:= ReplacedString( str, "ü", 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> –", 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