GAP 4.8.9 installation with standard packages -- copy to your CoCalc project to get it
#############################################################################
##
#W brdbattr.gi GAP 4 package `browse' Thomas Breuer
##
#Y Copyright (C) 2007, Lehrstuhl D für Mathematik, RWTH Aachen, Germany
##
## This file contains the implementations for tools for database handling.
##
#############################################################################
##
#F DatabaseIdEnumerator( <arec> )
##
InstallGlobalFunction( DatabaseIdEnumerator, function( arec )
local comps, entry;
arec:= ShallowCopy( arec );
# Check for the presence of the mandatory components.
comps:= [ [ "identifiers", "list", IsList ],
[ "entry", "function", IsFunction ],
];
for entry in comps do
if not IsBound( arec.( entry[1] ) )
or not entry[3]( arec.( entry[1] ) ) then
Error( "<arec>.", entry[1], " must be bound to a ", entry[2] );
fi;
od;
# Set default values for the optional components.
comps:= [ [ "attributes", "record", IsRecord, rec() ],
[ "isUpToDate", "function", IsFunction, ReturnTrue ],
[ "version", "object", IsObject, "" ],
[ "update", "function", IsFunction, ReturnTrue ],
[ "viewLabel", "table cell data object",
BrowseData.IsBrowseTableCellData, "name" ],
[ "viewValue", "function", IsFunction, String ],
[ "viewSort", "function", IsFunction, \< ],
[ "sortParameters", "list", IsList, [] ],
[ "widthCol", "positive integer", IsPosInt ],
[ "align", "string", IsString, "r" ],
[ "categoryValue", "function", IsFunction ],
[ "isSorted", "boolean", IsBool, false ],
];
for entry in comps do
if IsBound( arec.( entry[1] ) ) then
if not entry[3]( arec.( entry[1] ) ) then
Error( "<arec>.", entry[1], ", if bound, must be a ", entry[2] );
fi;
elif IsBound( entry[4] ) then
arec.( entry[1] ):= entry[4];
fi;
od;
if not IsBound( arec.categoryValue ) then
arec.categoryValue:= arec.viewValue;
fi;
# Set the "self" attribute.
DatabaseAttributeAdd( arec, rec(
identifier:= "self",
description:= "the identifiers themselves",
type:= "values",
data:= arec.identifiers,
version:= arec.version,
update:= function( a )
a.data:= a.idenumerator.identifiers;
return true;
end,
viewLabel:= arec.viewLabel,
viewValue:= arec.viewValue,
viewSort:= arec.viewSort,
sortParameters:= arec.sortParameters,
align:= arec.align,
categoryValue:= arec.categoryValue,
) );
if IsBound( arec.widthCol ) then
arec.attributes.self.widthCol:= arec.widthCol;
fi;
return arec;
end );
#############################################################################
##
#F DatabaseAttributeAdd( <dbidenum>, <arec> )
##
InstallGlobalFunction( DatabaseAttributeAdd, function( dbidenum, arec )
local comps, entry;
# Check `dbidenum'.
if not IsRecord( dbidenum ) or not IsBound( dbidenum.attributes )
or not IsRecord( dbidenum.attributes ) then
Error( "<dbidenum> must be a database id enumerator" );
elif IsBound( arec.identifier ) and
IsBound( dbidenum.attributes.( arec.identifier ) ) then
Error( "an attribute with identifier `", arec.identifier,
"' is already bound in <dbidenum>" );
fi;
arec:= ShallowCopy( arec );
arec.idenumerator:= dbidenum;
# Check for the presence of the mandatory components.
comps:= [ [ "identifier", "string", IsString ],
[ "type", "string", IsString ],
];
for entry in comps do
if not IsBound( arec.( entry[1] ) )
or not entry[3]( arec.( entry[1] ) ) then
Error( "<arec>.", entry[1], " must be bound to a ", entry[2] );
fi;
od;
# Do more tests.
if not arec.type in [ "values", "pairs" ] then
Error( "<arec>.type must be one of `\"values\"', `\"pairs\"'" );
fi;
# Set default values for the optional components.
comps:= [
[ "description", "string", IsString, "" ],
[ "name", "string", IsString ],
[ "datafile", "string", IsString ],
[ "attributeValue", "function", IsFunction,
DatabaseAttributeValueDefault ],
[ "dataDefault", "object", IsObject, "" ],
[ "eval", "function", IsFunction ],
[ "neededAttributes", "list", IsList, [] ],
[ "prepareAttributeComputation", "function", IsFunction,
ReturnTrue ],
[ "cleanupAfterAttibuteComputation", "function", IsFunction,
ReturnTrue ],
[ "create", "function", IsFunction ],
[ "string", "function", IsFunction,
# function( id, val ) return String( val ); end ],
String ],
[ "check", "function", IsFunction, ReturnTrue ],
[ "viewLabel", "table cell data object",
BrowseData.IsBrowseTableCellData ],
[ "viewValue", "function", IsFunction, String ],
[ "viewSort", "function", IsFunction, \< ],
[ "sortParameters", "list", IsList, [] ],
[ "widthCol", "positive integer", IsPosInt ],
[ "align", "string", IsString, "r" ],
[ "categoryValue", "function", IsFunction ],
];
for entry in comps do
if IsBound( arec.( entry[1] ) ) then
if not entry[3]( arec.( entry[1] ) ) then
Error( "<arec>.", entry[1], ", if bound, must be a ", entry[2] );
fi;
elif IsBound( entry[4] ) then
arec.( entry[1] ):= entry[4];
fi;
od;
# Do more tests.
if IsBound( arec.data ) then
if arec.type = "values" and not IsList( arec.data ) then
Error( "<arec>.type is \"values\", so <arec>.data must be a list" );
elif arec.type = "pairs" and
not ( IsRecord( arec.data ) and IsBound( arec.data.automatic ) and
IsBound( arec.data.nonautomatic ) ) then
Error( "<arec>.type is \"pairs\", so <arec>.data must be a record ",
"with the components `automatic' and `nonautomatic'" );
fi;
fi;
if IsBound( arec.name ) then
if not IsBoundGlobal( arec.name ) or
not IsFunction( ValueGlobal( arec.name ) ) then
Error( "<arec>.name must be the identifier of a global function" );
fi;
fi;
if IsBound( arec.isSorted ) then
if arec.type = "values" then
Error( "<arec>.isSorted is valid only for <arec>.type = \"pairs\"" );
elif not arec.isSorted in [ true, false ] then
Error( "<arec>.isSorted must be `true' or `false'" );
fi;
elif arec.type = "pairs" then
arec.isSorted:= false;
fi;
# Set default values for the optional components.
if not IsBound( arec.create ) and IsBound( arec.name ) then
arec.create:= function( attr, id )
return ValueGlobal( arec.name )(
attr.idenumerator.entry( attr.idenumerator, id ) );
end;
fi;
if not IsBound( arec.viewLabel ) then
if IsBound( arec.name ) then
arec.viewLabel:= arec.name;
else
arec.viewLabel:= arec.identifier;
fi;
fi;
if not IsBound( arec.categoryValue ) then
arec.categoryValue:= arec.viewValue;
fi;
if not IsBound( arec.version ) and not IsBound( arec.datafile ) then
arec.version:= dbidenum.version;
fi;
dbidenum.attributes.( arec.identifier ):= arec;
#T update component for attributes!
#T (when can I set a default value?)
#T where do I just have to replace known values?
end );
#############################################################################
##
#F DatabaseAttributeValueDefault( <attr>, <id> )
##
InstallGlobalFunction( DatabaseAttributeValueDefault, function( attr, id )
local pos, comp, result;
# If the `data' component is not bound then initialize it.
if not IsBound( attr.data ) then
if IsBound( attr.datafile ) and IsReadableFile( attr.datafile ) then
Read( attr.datafile );
else
DatabaseAttributeCompute( attr.idenumerator, attr.identifier );
fi;
fi;
if not IsBound( attr.data ) then
Error( "<attr>.data is still not bound" );
fi;
if attr.type = "values" then
if attr.idenumerator.isSorted then
pos:= PositionSet( attr.idenumerator.identifiers, id );
else
pos:= Position( attr.idenumerator.identifiers, id );
fi;
if pos <> fail then
if IsBound( attr.data[ pos ] ) then
result:= attr.data[ pos ];
elif IsBound( attr.name ) then
result:= attr.create( attr, id );
attr.data[ pos ]:= result;
else
result:= attr.dataDefault;
fi;
fi;
elif attr.isSorted then
for comp in [ attr.data.automatic, attr.data.nonautomatic ] do
pos:= PositionSorted( comp, [ id ] );
if pos <= Length( comp ) and comp[ pos ][1] = id then
result:= comp[ pos ][2];
break;
fi;
od;
else
for comp in [ attr.data.automatic, attr.data.nonautomatic ] do
pos:= First( [ 1 .. Length( comp ) ], i -> comp[i][1] = id );
if pos <> fail then
result:= comp[ pos ][2];
break;
fi;
od;
fi;
if not IsBound( result ) then
if IsBound( attr.dataDefault ) then
result:= attr.dataDefault;
else
Error( "no `dataDefault' entry" );
fi;
fi;
if IsBound( attr.eval ) then
result:= attr.eval( attr, result );
fi;
return result;
end );
#############################################################################
##
#F DatabaseAttributeSetData( <dbidenum>, <attridentifier>, <version>,
#F <data> )
##
InstallGlobalFunction( DatabaseAttributeSetData,
function( dbidenum, attridentifier, version, data )
local attr;
if not IsRecord( dbidenum ) or not IsBound( dbidenum.attributes )
or not IsRecord( dbidenum.attributes ) then
Error( "usage: DatabaseAttributeSetData( <dbidenum>, ",
"<attridentifier>,\n <version>, <data> )" );
elif not IsBound( dbidenum.attributes.( attridentifier ) ) then
Error( "<dbidenum> has no attribute `", attridentifier, "'" );
fi;
attr:= dbidenum.attributes.( attridentifier );
if not ( ( attr.type = "values" and IsList( data ) ) or
( attr.type = "pairs" and IsRecord( data ) ) ) then
Error( "<data> does not fit to the type of <attr>" );
fi;
if attr.type = "pairs" then
if attr.isSorted = true and
( not IsSSortedList( data.automatic ) or
not IsSSortedList( data.nonautomatic ) ) then
Error( "the data lists are not strictly sorted" );
fi;
if not IsEmpty( Intersection( List( data.automatic, x -> x[1] ),
List( data.nonautomatic, x -> x[1] ) ) ) then
#T provide an NC variant that skips the tests?
Error( "automatic and nonautomatic data are not disjoint" );
fi;
attr.data:= data;
else
if Length( dbidenum.identifiers ) < Length( data ) then
Error( "automatic and nonautomatic data are not disjoint" );
fi;
attr.data:= data;
fi;
attr.version:= version;
#T What shall happen if the version does not fit to dbidenum?
end );
#############################################################################
##
#F DatabaseIdEnumeratorUpdate( <dbidenum> )
##
InstallGlobalFunction( DatabaseIdEnumeratorUpdate, function( dbidenum )
local name, attr;
if dbidenum.update( dbidenum ) <> true then
Info( InfoDatabaseAttribute, 1,
"DatabaseIdEnumeratorUpdate: <dbidenum>.update returned ",
"'false'" );
return false;
fi;
#T do this in the order prescribed by neededAttributes!
for name in RecNames( dbidenum.attributes ) do
attr:= dbidenum.attributes.( name );
if not IsBound( attr.version ) then
if IsBound( attr.datafile ) and IsReadableFile( attr.datafile ) then
Read( attr.datafile );
fi;
#T default value if `name' is bound?
if not IsBound( attr.version ) then
Error( "<attr>.version still not bound" );
fi;
fi;
if attr.version <> dbidenum.version then
if IsBound( attr.update ) and attr.update( attr ) = true then
attr.version:= dbidenum.version;
else
Info( InfoDatabaseAttribute, 1,
"DatabaseIdEnumeratorUpdate: <attr>.update for attribute '",
name, "' returned 'false'" );
return false;
fi;
fi;
od;
return true;
end );
#############################################################################
##
#F DatabaseAttributeCompute( <dbidenum>, <attridentifier>[, <what>] )
##
InstallGlobalFunction( DatabaseAttributeCompute, function( arg )
local idenum, attridentifier, what, attr, attrid, attr2, i, new,
oldnonautomatic, oldautomatic, automatic, newautomatic, id;
idenum:= arg[1];
attridentifier:= arg[2];
what:= "automatic";
if Length( arg ) = 3 and arg[3] in [ "all", "automatic", "new" ] then
what:= arg[3];
fi;
if not IsRecord( idenum ) or
not IsBound( idenum.attributes ) or
not IsRecord( idenum.attributes ) or
not IsString( attridentifier ) or
not IsBound( idenum.attributes.( attridentifier ) ) then
Info( InfoDatabaseAttribute, 1,
"<idenum> has no component <attridentifier>" );
return false;
fi;
attr:= idenum.attributes.( attridentifier );
if not IsBound( attr.create ) then
Info( InfoDatabaseAttribute, 1,
"<attr> has no component <create>" );
return false;
fi;
# Update the needed attributes if necessary.
for attrid in attr.neededAttributes do
attr2:= idenum.attributes.( attrid );
if not IsBound( attr2.version ) and not IsBound( attr2.data ) and
IsBound( attr2.datafile ) and IsReadableFile( attr2.datafile ) then
Read( attr2.datafile );
fi;
if not IsBound( attr2.version ) or attr2.version <> idenum.version then
Info( InfoDatabaseAttribute, 1,
"DatabaseAttributeCompute for attribute ", attridentifier,
":\n#I compute needed attribute ", attrid );
DatabaseAttributeCompute( idenum, attrid, what );
fi;
od;
attr.prepareAttributeComputation( attr );
if attr.type = "values" then
if what = "automatic" then
what:= "all";
fi;
if not IsBound( attr.data ) then
# Fetch the known values; if necessary then initialize.
if IsBound( attr.datafile ) and IsReadableFile( attr.datafile ) then
Read( attr.datafile );
else
attr.data:= [];
fi;
fi;
Info( InfoDatabaseAttribute, 1,
"DatabaseAttributeCompute: start for attribute ",
attridentifier );
for i in [ 1 .. Length( idenum.identifiers ) ] do
if ( not IsBound( attr.data[i] ) ) or ( what <> "new" ) then
new:= attr.create( attr, idenum.identifiers[i] );
if IsBound( attr.data[i] ) then
if IsBound( attr.dataDefault ) and new = attr.dataDefault then
Info( InfoDatabaseAttribute, 2,
"difference in recompute for ", idenum.identifiers[i],
":\n#E deleting entry\n", attr.data[i] );
Unbind( attr.data[i] );
elif new <> attr.data[i] then
Info( InfoDatabaseAttribute, 2,
"difference in recompute for ", idenum.identifiers[i],
":\n#E replacing entry\n#E ", attr.data[i],
"\n#E by\n#E ", new );
attr.data[i]:= new;
fi;
elif not IsBound( attr.dataDefault ) or new <> attr.dataDefault then
Info( InfoDatabaseAttribute, 2,
"recompute: new entry for ", idenum.identifiers[i],
":\n#I ", new );
attr.data[i]:= new;
fi;
fi;
od;
Info( InfoDatabaseAttribute, 1,
"DatabaseAttributeCompute: done for attribute ",
attridentifier );
attr.version:= idenum.version;
else
if not IsBound( attr.data ) then
# Fetch the known values; if necessary then initialize.
if IsBound( attr.datafile ) and IsReadableFile( attr.datafile ) then
Read( attr.datafile );
else
attr.data:= rec( automatic:= [], nonautomatic:= [] );
fi;
fi;
oldnonautomatic:= List( attr.data.nonautomatic, x -> x[1] );
oldautomatic:= List( attr.data.automatic, x -> x[1] );
automatic:= [];
newautomatic:= [];
Info( InfoDatabaseAttribute, 1,
"DatabaseAttributeCompute: start for attribute ",
attridentifier );
for id in idenum.identifiers do
if not ( ( what in [ "automatic", "new" ] and id in oldnonautomatic ) or
( what = "new" and id in oldautomatic ) ) then
new:= attr.create( attr, id );
if new <> attr.dataDefault then
Add( automatic, [ id, new ] );
# Handle the case that a nonautomatic value becomes automatic.
if what = "all" and id in oldnonautomatic then
Info( InfoDatabaseAttribute, 2,
"recompute: formerly nonautomatic value for ", id,
"#I is now automatic" );
Add( newautomatic, id );
fi;
fi;
fi;
od;
attr.data.automatic:= automatic;
if newautomatic <> [] then
attr.data.nonautomatic:= Filtered( attr.data.nonautomatic,
pair -> not pair[1] in newautomatic );
fi;
Info( InfoDatabaseAttribute, 1,
"DatabaseAttributeCompute: done for attribute ",
attridentifier );
attr.version:= idenum.version;
fi;
attr.cleanupAfterAttibuteComputation( attr );
return true;
end );
#############################################################################
##
#F DatabaseAttributeString( idenum, idenumname, attridentifier )
##
InstallGlobalFunction( DatabaseAttributeString,
function( idenum, idenumname, attridentifier )
local attr, str, strfun, txt, comp, entry;
if not IsBound( idenum.attributes.( attridentifier ) ) then
Error( "<idenum> has no component <attridentifier>" );
fi;
attr:= idenum.attributes.( attridentifier );
if not IsBound( attr.data ) then
if IsBound( attr.datafile ) and IsReadableFile( attr.datafile ) then
Read( attr.datafile );
else
DatabaseAttributeCompute( idenum, attridentifier );
fi;
fi;
str:= Concatenation( "DatabaseAttributeSetData( ", idenumname, ", \"",
attridentifier, "\",\n" );
if IsString( attr.version ) then
Append( str, Concatenation( "\"", attr.version, "\"," ) );
else
Append( str, Concatenation( String( attr.version ), "," ) );
fi;
if attr.type = "values" then
Print( "missing code!\n" );
else
Append( str, "rec(\nautomatic:=[\n" );
if IsBound( attr.string ) then
strfun:= attr.string;
else
strfun:= String;
fi;
txt:= "],\nnonautomatic:=[\n";
for comp in [ attr.data.automatic, attr.data.nonautomatic ] do
for entry in comp do
if entry[2] <> attr.dataDefault then
Append( str, strfun( entry ) );
fi;
od;
Append( str, txt );
txt:= "]));\n";
od;
fi;
return str;
end );
#############################################################################
##
#F BrowseTableFromDatabaseIdEnumerator( <dbidenum>, <labelids>, <columnids>
#F [, <header>[, <footer>[, <choice>]]] )
##
InstallGlobalFunction( BrowseTableFromDatabaseIdEnumerator,
function( arg )
local dbidenum, labelids, columnids, header, footer, choice, aligned,
identifiers, labels, columns, id, widthLabelsRow, widthCol,
sortFunctions, categoryValues, formattedValue, i, table;
# Get and check the arguments.
if not Length( arg ) in [ 3 .. 6 ] then
Error( "usage: BrowseTableFromDatabaseIdEnumerator( <dbidenum>,\n",
" <labelids>, <columnids>[, <header>[, <footer>",
"[, <choice>]]] )" );
fi;
dbidenum:= arg[1];
if not ( IsRecord( dbidenum ) and IsBound( dbidenum.attributes ) ) then
Error( "<dbidenum> must be a database id enumerator" );
fi;
labelids:= arg[2];
if not ( IsList( labelids ) and
ForAll( labelids,
x -> IsBound( dbidenum.attributes.( x ) ) ) ) then
Error( "<labelids> must be a list of attribute identifiers for ",
"<dbidenum>" );
fi;
columnids:= arg[3];
if not ( IsList( columnids ) and not IsEmpty( columnids ) and
ForAll( columnids,
x -> IsBound( dbidenum.attributes.( x ) ) ) ) then
Error( "<columnids> must be a nonempty list of attribute identifiers ",
"for <dbidenum>" );
fi;
header:= [];
footer:= [];
choice:= dbidenum.identifiers;
if Length( arg ) = 4 then
header:= arg[4];
elif Length( arg ) = 5 then
header:= arg[4];
footer:= arg[5];
elif Length( arg ) = 6 then
header:= arg[4];
footer:= arg[5];
choice:= arg[6];
fi;
aligned:= function( list, align )
local max;
if IsEmpty( list ) then
return list;
fi;
max:= Maximum( List( list, Length ) );
if align = "left" then
max:= max;
fi;
return List( list, x -> String( x, max ) );
end;
labels:= List( labelids, id -> dbidenum.attributes.( id ) );
columns:= List( columnids, id -> dbidenum.attributes.( id ) );
widthLabelsRow:= [];
for i in [ 1 .. Length( labels ) ] do
if IsBound( labels[i].widthCol ) then
widthLabelsRow[ 2*i ]:= labels[i].widthCol;
fi;
od;
# Set fixed width where the attribute forces this.
# Set sort functions.
# Set alignments.
# Set the functions for computing category values.
widthCol:= [];
sortFunctions:= List( columns, x -> x.viewSort );
categoryValues:= List( columns, x -> x.categoryValue );
for i in [ 1 .. Length( columns ) ] do
if IsBound( columns[i].widthCol ) then
widthCol[ 2*i ]:= columns[i].widthCol;
fi;
od;
# Compute an attribute value, and the table cell data object.
formattedValue:= function( attr, j, id, width )
local val, align;
# Compute the attribute value, and the table cell data object.
val:= attr.viewValue( attr.attributeValue( attr, id ) );
# Format the entry.
if not IsRecord( val ) then
if IsBound( width[ 2*j ] ) then
if 'l' in attr.align then
align:= "left";
elif 'r' in attr.align then
align:= "right";
else
align:= "";
fi;
if IsString( val ) then
if align <> "" then
# It is documented that non-strings need no additional
# formatting if the column width is prescribed.
val:= rec( rows:= aligned( SplitString(
BrowseData.ReallyFormatParagraph( val,
width[ 2*j ], align ), "\n" ),
align ),
align:= attr.align );
else
val:= rec( rows:= [ val ], align:= attr.align );
fi;
fi;
else
val:= rec( rows:= [ val ], align:= attr.align );
fi;
fi;
return val;
end;
# Construct the browse table.
table:= rec(
work:= rec(
align:= "tl",
header:= header,
main:= [],
Main:= function( t, i, j )
return formattedValue( columns[j], j, choice[i], widthCol );
end,
m:= Length( choice ),
n:= Length( columns ),
corner:= [ List( labels, x -> rec( rows:= [ x.viewLabel ],
align:= x.align ) ) ],
labelsRow:= TransposedMat( List( [ 1 .. Length( labels ) ],
j -> List( choice,
y -> formattedValue( labels[j], j, y, widthLabelsRow ) ) ) ),
labelsCol:= [ List( columns,
x -> rec( rows:= [ x.viewLabel ],
align:= x.align ) ) ],
sepLabelsRow:= [],
sepLabelsCol:= "=",
sepRow:= "-",
sepCol:= Concatenation( [ "| " ],
List( [ 2 .. Length( columns ) ], x -> " | " ),
[ " |" ] ),
widthLabelsRow:= ShallowCopy( widthLabelsRow ),
widthCol:= ShallowCopy( widthCol ),
SpecialGrid:= BrowseData.SpecialGridLineDraw,
CategoryValues:= function( t, i, j )
local val;
i:= i / 2;
j:= j / 2;
val:= columns[j].attributeValue( columns[j], choice[i] );
val:= categoryValues[j]( val );
return BrowseData.CategoryValuesFromEntry( t, val, j );
end,
),
dynamic:= rec(
sortFunctionsForColumns:= sortFunctions,
),
);
if not IsEmpty( labels ) then
table.work.sepLabelsRow:= Concatenation( [ "| " ],
List( [ 2 .. Length( labels ) ], x -> " | " ),
[ " |" ] );
fi;
# Customize the sort parameters for the columns.
for i in [ 1 .. Length( columns ) ] do
if not IsEmpty( columns[i].sortParameters ) then
BrowseData.SetSortParameters( table, "column", i,
columns[i].sortParameters );
fi;
od;
# Return the browse table.
return table;
end );
#############################################################################
##
#E