# AutoDoc: Generate documentation from GAP source code # # Copyright of AutoDoc belongs to its developers. # Please refer to the COPYRIGHT file for details. # # SPDX-License-Identifier: GPL-2.0-or-later
# Given a string containing a ".", , return its suffix, # i.e. the bit after the last ".". For example, given "test.txt", # it returns "txt".
BindGlobal( "AUTODOC_GetSuffix", function(str)
local i;
i := Length(str); while i > 0 and str[i] <> '.'do i := i - 1; od; if i = 0then return ""; fi;
return str{[i+1..Length(str)]}; end );
# Scan the given (by name) subdirs of a package dir for # files with one of the given extensions, and return the corresponding # filenames, as relative paths (relative to the package dir). # # For example, the invocation # AUTODOC_FindMatchingFiles(pkgdir, [ "gap/" ], [ "gi", "gd" ]); # might return a list looking like # [ "gap/AutoDocMainFunction.gd", "gap/AutoDocMainFunction.gi", ... ]
BindGlobal( "AUTODOC_FindMatchingFiles", function (pkgdir, subdirs, extensions)
local d_rel, d, tmp, files, result;
result := [];
for d_rel in subdirs do # Get the absolute path to the directory in side the package...
d := Filename( pkgdir, d_rel ); if not IsDirectoryPath( d ) then
continue; fi;
d := Directory( d ); # ... but also keep the relative path (such as "gap") if d_rel = "" or d_rel = "."then
d_rel := ""; else
d_rel := Directory( d_rel ); fi;
files := DirectoryContents( d );
Sort( files ); for tmp in files do if not AUTODOC_GetSuffix( tmp ) in extensions then
continue; fi; if not IsReadableFile( Filename( d, tmp ) ) then
continue; fi; if d_rel = ""then
Add( result, tmp ); else
Add( result, Filename( d_rel, tmp ) ); fi;
od;
od;
return result; end );
if Length( arg ) >= 3then
Error( "too many arguments" ); fi;
# check whether the last argument is an options record if Length( arg ) > 0 and IsRecord( arg[Length(arg)] ) then
opt := Remove( arg ); else
opt := rec(); fi;
# check the first argument if Length(arg) = 0then
pkgdir := DirectoryCurrent( ); elif IsString( arg[1] ) then
pkgname := Remove( arg, 1 ); elif IsDirectory( arg[1] ) then
pkgdir := Remove( arg, 1 ); fi;
# if there are any arguments left, at least one was of unsupported type if Length(arg) > 0then
Error( "wrong arguments" ); fi;
if IsBound( pkgdir ) then
is_worksheet := false;
file := Filename( pkgdir, "PackageInfo.g" ); if not IsExistingFile( file ) then
Error( "no package name given and no PackageInfo.g file found" ); elif not IsReadableFile( file ) then
Error( "cannot read PackageInfo.g" ); fi;
Unbind( GAPInfo.PackageInfoCurrent );
Read( file ); if not IsBound( GAPInfo.PackageInfoCurrent ) then
Error( "reading PackageInfo.g failed" ); fi;
pkginfo := GAPInfo.PackageInfoCurrent; if IsRecord( pkginfo.PackageDoc ) then
pkginfo.PackageDoc:= [ pkginfo.PackageDoc ]; fi;
pkgname := pkginfo.PackageName;
elif pkgname = "AutoDocWorksheet"then # For internal use only -- for details, refer to the AutoDocWorksheet() function.
is_worksheet := true;
pkginfo := rec( );
pkgdir := DirectoryCurrent( );
else
is_worksheet := false;
pkginfo := PackageInfo( pkgname ); if IsEmpty( pkginfo ) then
Error( "Could not find package ", pkgname ); elif Length( pkginfo ) > 1then
Info( InfoWarning, 1, "multiple versions of package ", pkgname, " are present, using the first one" ); fi;
pkginfo := pkginfo[ 1 ];
pkgdir := Directory( pkginfo.InstallationPath ); fi;
# # Check for user supplied options. If present, they take # precedence over any defaults as well as the opt record. # for key in [ "dir", "scaffold", "autodoc", "gapdoc", "extract_examples" ] do
val := ValueOption( key ); if val <> fail then
opt.(key) := val; fi;
od;
# # Setup the output directory # if not IsBound( opt.dir ) then
doc_dir := "doc"; elif IsString( opt.dir ) or IsDirectory( opt.dir ) then
doc_dir := opt.dir; else
Error( "opt.dir must be a string containing a path, or a directory object" ); fi;
if IsString( doc_dir ) then # Record the relative version of the path # FIXME: this assumes that doc_dir contains a relative path in the first place...
doc_dir_rel := Directory( doc_dir );
# We intentionally do not use # DirectoriesPackageLibrary( pkgname, "doc" ) # because it returns an empty list if the subdirectory is missing. # But we want to handle that case by creating the directory.
doc_dir := Filename( pkgdir, doc_dir );
doc_dir := Directory( doc_dir );
else # In this case, if doc_dir happens to lie below pkgdir, we want the # doc_dir_rel to be the difference; if not we avoid binding doc_dir_rel # and leave MakeGAPDocDoc to muddle through with absolute paths.
pkgdirstr := Filename( pkgdir, "" );
docdirstr := Filename( doc_dir, "" ); if StartsWith( docdirstr, pkgdirstr ) then
doc_dir_rel :=
Directory( docdirstr{[(Length(pkgdirstr)+1)..Length(docdirstr)]} ); fi; fi;
# Ensure the output directory exists, create it if necessary
AUTODOC_CreateDirIfMissing(Filename(doc_dir, ""));
# Let the developer know where we are generating the documentation. # This helps diagnose problems where multiple instances of a package # are visible to GAP and the wrong one is used for generating the # documentation.
Info( InfoAutoDoc, 2, "Generating documentation in ", doc_dir);
# # Extract scaffolding settings, which can be controlled via # opt.scaffold or pkginfo.AutoDoc. The former has precedence. # if not IsBound(opt.scaffold) then # Default: enable scaffolding if and only if pkginfo.AutoDoc is present if IsBound( pkginfo.AutoDoc ) then
scaffold := rec( ); fi; elif IsRecord(opt.scaffold) then
scaffold := opt.scaffold; elif IsBool(opt.scaffold) then if opt.scaffold = true then
scaffold := rec(); fi; else
Error("opt.scaffold must be a bool or a record"); fi;
# Merge pkginfo.AutoDoc into scaffold if IsBound(scaffold) and IsBound( pkginfo.AutoDoc ) then for key in RecNames( pkginfo.AutoDoc ) do if IsBound( scaffold.(key) ) then
Info(InfoAutoDoc, 1, "WARNING: ", key, " specified in both PackageInfo.AutoDoc and opt.scaffold"); else
scaffold.(key) := pkginfo.AutoDoc.(key); fi;
od;
fi;
if IsBound( scaffold ) then
AUTODOC_SetIfMissing( scaffold, "TitlePage", rec() );
AUTODOC_SetIfMissing( scaffold, "MainPage", true ); fi;
# # Extract AutoDoc settings # if not IsBound(opt.autodoc) and not is_worksheet then # Enable AutoDoc support if the package depends on AutoDoc.
tmp := Concatenation( pkginfo.Dependencies.NeededOtherPackages,
pkginfo.Dependencies.SuggestedOtherPackages ); ## Empty entries are allowed in Dependencies
tmp := Filtered( tmp, i -> i <> [ ] ); if ForAny( tmp, x -> LowercaseString(x[1]) = "autodoc" ) then
autodoc := rec(); fi; elif IsRecord(opt.autodoc) then
autodoc := opt.autodoc; elif IsBool(opt.autodoc) and opt.autodoc = true then
autodoc := rec(); fi;
if IsBound(autodoc) then if not IsBound( autodoc.files ) then
autodoc.files := [ ]; elif not IsList( autodoc.files ) then
Error("autodoc.files must be a list"); elif Length(autodoc.files) >0 and IsString( autodoc.files ) then
Error("autodoc.files must be a list of strings, not a string"); fi;
if not is_worksheet then if not IsBound( autodoc.scan_dirs ) then
autodoc.scan_dirs := [ ".", "gap", "lib", "examples", "examples/doc" ]; fi;
Append( autodoc.files, AUTODOC_FindMatchingFiles(pkgdir, autodoc.scan_dirs, [ "g", "gi", "gd", "autodoc" ]) );
autodoc.files := DuplicateFreeList( autodoc.files ); fi;
# Make sure all of the files exist, making the file names absolute if # necessary for i in [ 1 .. Length( autodoc.files ) ] do if IsExistingFile( autodoc.files[ i ] ) then continue; fi; if IsExistingFile( Filename( pkgdir, autodoc.files[ i ] ) ) then
autodoc.files[ i ] := Filename( pkgdir, autodoc.files[ i ] );
continue; fi;
Error( autodoc.files[ i ], " does not specify an existing file either as an absolute path or relative to the package directory" );
od;
if not IsBound( autodoc.level ) then
autodoc.level := 0; fi; fi;
# # Extract GAPDoc settings # if not IsBound( opt.gapdoc ) then # Enable GAPDoc support by default
gapdoc := rec(); elif IsRecord( opt.gapdoc ) then
gapdoc := opt.gapdoc; elif IsBool( opt.gapdoc ) and opt.gapdoc = true then
gapdoc := rec(); fi;
if IsBound( gapdoc ) then
AUTODOC_SetIfMissing( gapdoc, "main", pkgname );
if IsBound( pkginfo.PackageDoc ) and not IsEmpty( pkginfo.PackageDoc ) then if Length( pkginfo.PackageDoc ) > 1then
Info(InfoAutoDoc, 1, "WARNING: Package contains multiple books, only using the first one"); fi;
gapdoc.bookname := pkginfo.PackageDoc[1].BookName;
gapdoc.SixFile := pkginfo.PackageDoc[1].SixFile; elif not is_worksheet then # Default: book name = package name
gapdoc.bookname := pkgname;
gapdoc.SixFile := "doc/manual.six";
Info(InfoAutoDoc, 1, "WARNING: PackageInfo.g is missing a PackageDoc entry!");
Info(InfoAutoDoc, 1, "Without this, your package manual will not be recognized by the GAP help system.");
Info(InfoAutoDoc, 1, "You can correct this by adding the following to your PackageInfo.g:");
Info(InfoAutoDoc, 1, "PackageDoc := rec(");
Info(InfoAutoDoc, 1, " BookName := ~.PackageName,");
Info(InfoAutoDoc, 1, " ArchiveURLSubset := [\"doc\"],");
Info(InfoAutoDoc, 1, " HTMLStart := \"doc/chap0.html\",");
Info(InfoAutoDoc, 1, " PDFFile := \"doc/manual.pdf\",");
Info(InfoAutoDoc, 1, " SixFile := \"doc/manual.six\",");
Info(InfoAutoDoc, 1, " LongTitle := ~.Subtitle,");
Info(InfoAutoDoc, 1, "),"); fi;
if not IsBound( gapdoc.files ) then
gapdoc.files := []; elif not IsList( gapdoc.files ) then
Error("gapdoc.files must be a list"); elif not ForAll( gapdoc.files, IsString ) then
Error("gapdoc.files must be a list of strings, not a string"); fi;
if not is_worksheet then if not IsBound( gapdoc.scan_dirs ) then
gapdoc.scan_dirs := [ ".", "gap", "lib", "examples", "examples/doc" ]; fi;
Append( gapdoc.files, AUTODOC_FindMatchingFiles(pkgdir, gapdoc.scan_dirs, [ "g", "gi", "gd" ]) ); fi;
# Attempt to weed out duplicates as they may confuse GAPDoc (this # will not work if there are any non-normalized paths in the list).
gapdoc.files := Set( gapdoc.files );
# If possible, convert the file paths in gapdoc.files, which are # relative to the package directory, to paths which are relative to # the doc directory.
if IsBound( doc_dir_rel ) then # For this, we assume that doc_dir_rel is normalized (e.g. # it does not contains '//') and relative. # FIXME: this is an ugly hack, can't we do something better?
tmp := Number( Filename( doc_dir_rel, "" ), x -> x = '/' );
tmp := Concatenation( ListWithIdenticalEntries(tmp, "../") );
gapdoc.files := List( gapdoc.files, f -> Concatenation( tmp, f ) ); else # Here presumably the doc_dir was given by an absolute path that # does not lie below the package dir. In that case, we can't make # the gapdoc.files relative to the doc dir, but rather we have no # choice but to make them absolute, which MakeGAPDocDoc can handle, # even if perhaps less gracefully/portably.
gapdoc.files := List( gapdoc.files, f -> Filename( pkgdir, f ) ); fi; fi;
# read tree # FIXME: shouldn't tree be declared inside of an 'if IsBound(autodoc)' section?
tree := DocumentationTree( );
if IsBound( autodoc ) then if IsBound( autodoc.section_intros ) then
AUTODOC_PROCESS_INTRO_STRINGS( autodoc.section_intros, tree ); fi;
AutoDocScanFiles( autodoc.files, pkgname, tree ); fi;
if is_worksheet then # FIXME: We use scaffold and autodoc here without checking whether # they are bound. Does that mean worksheets always use them? if IsBound( scaffold.TitlePage.Title ) then
pkgname := scaffold.TitlePage.Title;
elif IsBound( tree!.TitlePage.Title ) then
pkgname := tree!.TitlePage.Title;
if IsBound( autodoc ) then # If scaffold.includes is already set, then we add # AutoDocMainFile.xml to it, but *only* if it not already # there. This way, package authors can control where # it is put in their includes list. if not _AUTODOC_GLOBAL_OPTION_RECORD.AutoDocMainFile in scaffold.includes then
Add( scaffold.includes, _AUTODOC_GLOBAL_OPTION_RECORD.AutoDocMainFile ); fi; fi;
if IsBound( scaffold.bib ) and IsBool( scaffold.bib ) then if scaffold.bib = true then
scaffold.bib := Concatenation( pkgname, ".bib" ); else
Unbind( scaffold.bib ); fi; elif not IsBound( scaffold.bib ) then # If there is a doc/PKG.bib file, assume that we want to reference it in the scaffold.
tmp := Concatenation( pkgname, ".bib" ); if IsReadableFile( Filename( doc_dir, tmp ) ) then
scaffold.bib := tmp; fi; fi;
AUTODOC_SetIfMissing( scaffold, "index", true );
if IsBound( gapdoc ) then if AUTODOC_GetSuffix( gapdoc.main ) = "xml"then
scaffold.main_xml_file := gapdoc.main; else
scaffold.main_xml_file := Concatenation( gapdoc.main, ".xml" ); fi; fi;
if IsBound( scaffold.TitlePage ) and scaffold.TitlePage <> false then
title_page := ShallowCopy( scaffold.TitlePage );
if IsBound( scaffold.MainPage ) and scaffold.MainPage <> false then
CreateMainPage( gapdoc.bookname, doc_dir, scaffold ); fi; fi;
# # Write AutoDoc XML files # if IsBound( autodoc ) then
WriteDocumentation( tree, doc_dir, autodoc.level ); if IsBound( gapdoc ) then if IsBound( doc_dir_rel ) then
Add( gapdoc.files, "_Chunks.xml" ); else
Add( gapdoc.files, Filename( doc_dir, "_Chunks.xml" ) ); fi; fi; fi;
# # Run GAPDoc # if IsBound( gapdoc ) then
AUTODOC_SetIfMissing(gapdoc, "LaTeXOptions", rec() ); if not IsRecord( gapdoc.LaTeXOptions ) then
Error("gapdoc.LaTeXOptions must be a record"); fi; for key in RecNames( gapdoc.LaTeXOptions ) do if not IsString( gapdoc.LaTeXOptions.( key ) )
and IsList( gapdoc.LaTeXOptions.( key ) )
and LowercaseString( gapdoc.LaTeXOptions.( key )[ 1 ] ) = "file"then
gapdoc.LaTeXOptions.( key ) := StringFile( gapdoc.LaTeXOptions.( key )[ 2 ] ); fi;
od;
# Ask GAPDoc to use UTF-8 as input encoding for LaTeX, as the XML files # of the documentation are also in UTF-8 encoding, and may contain characters # not contained in the default Latin 1 encoding.
AUTODOC_SetIfMissing( gapdoc.LaTeXOptions, "InputEncoding", "utf8" );
SetGapDocLaTeXOptions( gapdoc.LaTeXOptions );
## HACK: If there is an empty index, MakeGAPDocDoc throws an error when creating the pdf. ## this addition prevents this by fake adding the index to the page number log. See issue 106. ## FIXME: Once an empty index is allowed in GapDoc, this should be removed.
GAPDoc2LaTeXProcs.Tail := Concatenation( "\\immediate\\write\\pagenrlog{[\"Ind\", 0, 0], \\arabic{page},}\n",
GAPDoc2LaTeXProcs.Tail );
# Don't try to generate PDFs if pdflatex is not available if Filename( DirectoriesSystemPrograms(), "pdflatex" ) = fail then
Add(args, "nopdf" ); fi;
# The global option "relativePath" can be set to ensure the manual # is built in such a way that all references to the GAP reference manual # are using relative file paths. This is mainly useful when building # a package manual for use in a distribution tarball.
tmp := ValueOption( "relativePath" );
if IsBound( gapdoc.gap_root_relative_path ) and tmp = fail then## the option overrides the settings in the call.
tmp := gapdoc.gap_root_relative_path; fi;
if tmp = true then
Add( args, "../../.." ); elif IsString( tmp ) then
Add( args, tmp ); fi;
# don't build PDF docs if the environment variable NOPDF is set if IsBound( GAPInfo.SystemEnvironment.NOPDF ) then
Add( args, "nopdf" ); fi;
# NOTE: We cannot just write CopyHTMLStyleFiles(doc_dir) here, as # CopyHTMLStyleFiles its argument directly to Directory(), leading # to an error in all GAP versions up to and including 4.8.6. This # will be fixed with GAP 4.9, where Directory() is made idempotent.
CopyHTMLStyleFiles( Filename( doc_dir, "" ) );
# The following (undocumented) API is there for compatibility # with old-style gapmacro.tex based package manuals. It # produces a manual.lab file which those packages can use if # they wish to link to things in the manual we are currently # generating. This can probably be removed eventually, but for # now, doing it does not hurt.
# FIXME: It seems that this command does not work if pdflatex # is not present. Maybe we should remove it.
if IsBound( gapdoc.SixFile ) then
file := Filename(pkgdir, gapdoc.SixFile); if file = fail or not IsReadableFile(file) then
Error("could not open `", file, "' for package `", pkgname, "'.\n"); fi;
GAPDocManualLabFromSixFile( gapdoc.bookname, file ); fi;
fi;
# # Handle extract_examples #
if IsBound( opt.extract_examples ) then if IsRecord( opt.extract_examples ) then
extract_examples := opt.extract_examples; elif opt.extract_examples = true then
extract_examples := rec( ); fi; fi;
if IsBound( extract_examples ) then if is_worksheet then # HACK: not even sure this is really what we want for worksheets, but # it is useful for our "dogfood" test suite
pkgdir := doc_dir; fi; if not IsBound( extract_examples.units ) then
extract_examples.units := "Chapter"; fi; if not IsBound( extract_examples.skip_empty_in_numbering ) then
extract_examples.skip_empty_in_numbering := true; fi;
AUTODOC_ExtractMyManualExamples( pkgname, pkgdir, doc_dir, gapdoc.main, gapdoc.files, extract_examples ); fi;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.