diff --git a/CHANGES.md b/CHANGES.md index 169d41fe..da6f66e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,8 +1,17 @@ This file describes changes in the AutoDoc package. ## unreleased + - Require GAP 4.13 or newer - Fix a spurious "chunk ... was defined but never inserted" warning for chunks that are only inserted from within the body of another chunk + - Add `AutoDocExtractExamples`, which extracts the manual examples of a + package into a temporary directory by running its `makedoc.g` without + building the manual. This allows running the examples from + `tst/testall.g` without storing generated `.tst` files in the + repository, and works from a read-only package directory + - Report the true origin of an extracted example: generated `.tst` files + now point at the `.autodoc` file or AutoDoc comment the example was + written in, instead of the intermediate XML file generated from it ## 2026.06.30 - Fix a regression in `.autodoc` parsing where Markdown-style headings diff --git a/PackageInfo.g b/PackageInfo.g index f8f17ce3..66a3ed01 100644 --- a/PackageInfo.g +++ b/PackageInfo.g @@ -118,10 +118,10 @@ PackageDoc := rec( ), Dependencies := rec( - GAP := ">= 4.11", + GAP := ">= 4.13", NeededOtherPackages := [ [ "GAPDoc", ">= 1.6.3" ] ], SuggestedOtherPackages := [ ], - TestPackages := [ [ "io", ">= 4.7.0" ] ], + TestPackages := [ ], ExternalConditions := [], ), diff --git a/doc/Tutorials.autodoc b/doc/Tutorials.autodoc index 6bb4ec71..9988999c 100644 --- a/doc/Tutorials.autodoc +++ b/doc/Tutorials.autodoc @@ -271,6 +271,21 @@ AutoDoc( rec( extract_examples := rec( subdir := "tst/generated" ) ) ); ``` This writes the extracted examples into tst/generated/ instead. +Either way the generated .tst files have to be regenerated whenever the +manual changes, which tempts one into committing them. To avoid that, extract +them when the tests run, using in your +tst/testall.g: +```@listing +LoadPackage( "mypkg" ); +dirs := DirectoriesPackageLibrary( "mypkg", "tst" ); +Add( dirs, AutoDocExtractExamples( "mypkg" ) ); +TestDirectory( dirs, rec( exitGAP := true ) ); +``` +This reads your makedoc.g, so the settings describing the manual are not +duplicated, but skips building the manual itself. Everything is written to a +temporary directory, so the extracted tests need not be committed and the tests +also run from a read-only package directory. + @Subsection Setting different &GAPDoc; options @SubsectionLabel Tut:IntegrateExisting:GapDocOptions diff --git a/gap/AutoDocMainFunction.gi b/gap/AutoDocMainFunction.gi index d60b71a2..7100adf7 100644 --- a/gap/AutoDocMainFunction.gi +++ b/gap/AutoDocMainFunction.gi @@ -360,9 +360,10 @@ end ); # The following function is based on code by Olexandr Konovalov BindGlobal("AUTODOC_ExtractMyManualExamples", -function( pkgname, pkgdir, docdir, main, files, opt ) - local tst, i, s, basename, name, output, ch, a, location, pos, comment, pkgdirString, - nonempty_units_found, number_of_digits, lpkgname, tstdir; +function( pkgname, pkgdir, docdir, main, files, opt, roots ) + local tst, i, s, basename, name, output, ch, a, location, pos, comment, + nonempty_units_found, number_of_digits, lpkgname, tstdir, composed, + prefixes, prefix; Info(InfoAutoDoc, 1, "Extracting manual examples for ", pkgname, " package ..."); lpkgname := LowercaseString(pkgname); @@ -371,9 +372,20 @@ function( pkgname, pkgdir, docdir, main, files, opt ) if not EndsWith(main, ".xml") then main := Concatenation( main, ".xml" ); fi; - tst:=ExtractExamples( docdir, main, files, opt.units ); + # This is GAPDoc's ExtractExamples, with a pass over the origin list added + # so that examples AutoDoc generated report the file they came from rather + # than the intermediate XML file. + composed := ComposedDocument( "GAPDoc", docdir, main, files, true ); + AUTODOC_RemapSourcePositions( composed[1], composed[2] ); + tst := ExtractExamplesXMLTree( + ParseTreeXMLString( composed[1], composed[2] ), opt.units ); Info(InfoAutoDoc, 1, Length(tst), " ", LowercaseString( opt.units ), "s detected"); - pkgdirString := Filename(pkgdir, ""); + # Directories a source file may live under, most specific first. Locations + # are reported relative to whichever matches, so that generated .tst files + # do not depend on where the package is installed. + prefixes := Concatenation( roots, [ pkgdir, + Directory(AUTODOC_CurrentDirectory()) ] ); + prefixes := List( prefixes, d -> Filename( d, "" ) ); if IsDirectory( opt.subdir ) then tstdir := Filename( opt.subdir, "" ); @@ -434,9 +446,20 @@ function( pkgname, pkgdir, docdir, main, files, opt ) AppendTo(output, "gap> START_TEST(\"", basename, "\");\n\n"); for a in ch do location := a[2][1]; - if StartsWith(location, pkgdirString) then - comment := location{[ Length(pkgdirString)+1 .. Length(location) ]}; + if not StartsWith(location, "/") then + # Already reproducible: AutoDoc recorded this position itself, + # or GAPDoc resolved it relative to the documentation dir. + comment := location; else + comment := fail; + for prefix in prefixes do + if StartsWith(location, prefix) then + comment := location{[ Length(prefix)+1 .. Length(location) ]}; + break; + fi; + od; + fi; + if comment = fail then pos := PositionSublist(location, LowercaseString(pkgname)); if pos <> fail then comment := location{[ pos+Length(pkgname)+1 .. Length(location) ]}; @@ -445,7 +468,10 @@ function( pkgname, pkgdir, docdir, main, files, opt ) if pos <> fail then comment := location{[ pos+2 .. Length(location) ]}; else - Error("oops"); + # Sources outside all of the above, e.g. worksheet + # inputs. The bare filename is still more useful than + # an absolute path, and keeps the output reproducible. + comment := Last( SplitString( location, "/" ) ); fi; fi; fi; diff --git a/gap/DocumentationTree.gi b/gap/DocumentationTree.gi index 8af53b2f..c1b07de4 100644 --- a/gap/DocumentationTree.gi +++ b/gap/DocumentationTree.gi @@ -666,7 +666,12 @@ end ); InstallMethod( WriteDocumentation, [ IsTreeForDocumentationVerbatimNodeRep, IsStream ], function( node, filestream ) - local line, attr_name; + local line, attr_name, marker; + + marker := AUTODOC_SourceMarker( node ); + if marker <> fail then + AppendTo( filestream, marker, "\n" ); + fi; AppendTo( filestream, "<", node!.element_name ); for attr_name in Set( RecNames( node!.attributes ) ) do diff --git a/gap/Examples.gd b/gap/Examples.gd new file mode 100644 index 00000000..2e8535d8 --- /dev/null +++ b/gap/Examples.gd @@ -0,0 +1,41 @@ +# 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 + +#! @Chapter Reference +#! @Section Extracting manual examples + +#! @Description +#! Extracts the examples from the manual of the package pkg and +#! returns the directory holding the resulting .tst files. +#! +#! pkg is either the name of a package or a directory object pointing +#! at one. The optional argument makedoc names the script to read, +#! and defaults to makedoc.g. +#! +#! The package's own makedoc.g is used as-is, so the settings which +#! describe the manual — its source files, scaffolding and +#! extract_examples options — are not duplicated. Only the parts of +#! the manual needed to collect the examples are built, no HTML or PDF is +#! produced, and everything is written to a temporary directory, so this +#! works even when the package directory is read-only. +#! +#! This is meant to be used from a package's tst/testall.g, so that +#! extracted tests need not be committed to the repository: +#! +#! +#! Note that makedoc.g is read in the usual way, so any other work it +#! performs still happens; and a script ending in QUIT cannot be used +#! this way. +#! @Returns a directory +#! @Arguments pkg[, makedoc] +DeclareGlobalFunction( "AutoDocExtractExamples" ); + +DeclareGlobalFunction( "AUTODOC_ExtractOnlyDirectory" ); diff --git a/gap/Examples.gi b/gap/Examples.gi new file mode 100644 index 00000000..87a0b317 --- /dev/null +++ b/gap/Examples.gi @@ -0,0 +1,87 @@ +# 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 + +# Environment variable equivalent of the AutoDocExtractOnly global option. It +# exists so that a makedoc.g ending in QUIT can still be driven, by running it +# as a separate GAP process. +BindGlobal( "AUTODOC_EXTRACT_ONLY_ENVVAR", "AUTODOC_EXTRACT_ONLY" ); + +BindGlobal( "AUTODOC_DEFAULT_MAKEDOC_FILE", "makedoc.g" ); + +# The scratch directory to work in, or fail if AutoDoc should build the manual +# normally. Both spellings carry the directory; passing just `true` or `1` +# asks for extract-only mode without naming one. +InstallGlobalFunction( "AUTODOC_ExtractOnlyDirectory", +function() + local value; + + value := ValueOption( "AutoDocExtractOnly" ); + if value = fail and + IsBound( GAPInfo.SystemEnvironment.( AUTODOC_EXTRACT_ONLY_ENVVAR ) ) then + value := GAPInfo.SystemEnvironment.( AUTODOC_EXTRACT_ONLY_ENVVAR ); + fi; + + if value = fail or value = false then + return fail; + fi; + + if IsDirectory( value ) then + return value; + fi; + + if value = true or value = "" or value = "1" then + return DirectoryTemporary(); + fi; + + if not IsString( value ) then + Error( "AutoDocExtractOnly must be true, a path, or a directory object" ); + fi; + + AUTODOC_CreateDirIfMissing( value ); + return Directory( value ); +end ); + +## +InstallGlobalFunction( "AutoDocExtractExamples", +function( pkg, makedoc... ) + local pkgdir, script, scratch, olddir; + + if Length( makedoc ) > 0 then + script := makedoc[ 1 ]; + else + script := AUTODOC_DEFAULT_MAKEDOC_FILE; + fi; + + if IsDirectory( pkg ) then + pkgdir := pkg; + elif IsString( pkg ) then + pkgdir := DirectoriesPackageLibrary( pkg, "" ); + if pkgdir = [ ] then + Error( "could not locate package ", pkg ); + fi; + pkgdir := pkgdir[ 1 ]; + else + Error( "pkg must be a package name or a directory object" ); + fi; + + script := Filename( pkgdir, script ); + if script = fail or not IsReadableFile( script ) then + Error( "could not read ", script ); + fi; + + scratch := DirectoryTemporary(); + + # AutoDoc() with no arguments picks up PackageInfo.g from the working + # directory, and makedoc.g scripts name their inputs relative to the + # package, so run the script from there. + olddir := AUTODOC_CurrentDirectory(); + ChangeDirectoryCurrent( Filename( pkgdir, "" ) ); + Read( script : AutoDocExtractOnly := scratch, nopdf ); + ChangeDirectoryCurrent( olddir ); + + return Directory( Filename( scratch, "tst" ) ); +end ); diff --git a/gap/Magic.gd b/gap/Magic.gd index 756e4785..f6122396 100644 --- a/gap/Magic.gd +++ b/gap/Magic.gd @@ -441,6 +441,23 @@ #! Also, if the environment variable `NOPDF` is set, then &AutoDoc; #! behaves as if the global option nopdf had been enabled. #! +#! AutoDocExtractOnly +#! +#! If this global option is set, &AutoDoc; builds only as much of the +#! manual as is needed to collect its examples: no HTML, PDF or manual +#! index is produced, and all output is written below the given scratch +#! directory instead of the package. The extracted tests end up in its +#! tst subdirectory. +#!

+#! The value is a directory, or `true` to have one chosen automatically. +#! If the environment variable `AUTODOC_EXTRACT_ONLY` is set, &AutoDoc; +#! behaves as if this option had been given; that is useful for a +#! makedoc.g which ends in QUIT and therefore has to be run +#! as a separate process. +#!

+#! Rather than setting this by hand, use +#! . +#! #! relativePath #! #! This has the same effect as gapdoc.gap_root_relative_path, but diff --git a/gap/Magic.gi b/gap/Magic.gi index 1d826cfa..256abf0f 100644 --- a/gap/Magic.gi +++ b/gap/Magic.gi @@ -88,9 +88,9 @@ function( is_worksheet, pkgname, pkginfo, pkgdir, opt ) local scaffold, gapdoc, extract_examples, autodoc, i, doc_dir, doc_dir_rel, tmp, key, val, file, pkgdirstr, docdirstr, - title_page, tree, + title_page, tree, source_anchor, position_document_class, - args, used_legacy_value_options; + args, used_legacy_value_options, extract_only, extract_roots; # # Deprecated feature: Check for user supplied global options. If present, @@ -109,6 +109,12 @@ function( is_worksheet, pkgname, pkginfo, pkgdir, opt ) Print("#W passing options via GAP's global options system is deprecated; use an option record instead\n"); fi; + # + # Extract-only mode: build just enough of the manual, in a scratch + # directory, to collect its examples. See AutoDocExtractExamples. + # + extract_only := AUTODOC_ExtractOnlyDirectory(); + # # Setup the output directory # @@ -144,6 +150,20 @@ function( is_worksheet, pkgname, pkginfo, pkgdir, opt ) fi; fi; + if extract_only <> fail then + # ComposedDocument resolves relative <#Include SYSTEM ...> against a + # single directory, and handwritten doc files routinely include + # generated ones by relative name. So stage the whole documentation + # directory and generate into the copy; the package directory is then + # never written to, and may even be read-only. + AUTODOC_StageDirectory( doc_dir, Directory( Filename( extract_only, "doc" ) ) ); + doc_dir := Directory( Filename( extract_only, "doc" ) ); + + # doc_dir no longer lies below pkgdir, so paths relative to it would + # be wrong; fall back to the absolute-path branches further down. + Unbind( doc_dir_rel ); + fi; + # Ensure the output directory exists, create it if necessary AUTODOC_CreateDirIfMissing(Filename(doc_dir, "")); @@ -333,7 +353,25 @@ function( is_worksheet, pkgname, pkginfo, pkgdir, opt ) tree := DocumentationTree( ); if IsBound( autodoc ) then - AutoDocScanFiles( autodoc.files, pkgname, tree ); + # Hand the parser both the real path and a reproducible name to + # report positions under; the latter ends up in generated files. + source_anchor := pkgdir; + if is_worksheet then + # A worksheet has no package directory: pkgdir is merely the + # working directory, which would make the recorded paths depend on + # where AutoDocWorksheet was called from, and on whether the inputs + # were reached through a symlink. Anchor on the directory the input + # files share instead. + tmp := AUTODOC_CommonParentDirectory( autodoc.files ); + if tmp <> fail then + source_anchor := Directory( tmp ); + fi; + fi; + AutoDocScanFiles( + List( autodoc.files, + f -> rec( path := f, + display := AUTODOC_RelativeSourcePath( f, source_anchor ) ) ), + pkgname, tree ); fi; if is_worksheet then @@ -564,30 +602,36 @@ function( is_worksheet, pkgname, pkginfo, pkgdir, opt ) fi; # Finally, invoke GAPDoc - CallFuncList( MakeGAPDocDoc, args ); - - # 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"); + if extract_only <> fail then + # Only the composed XML matters here; producing HTML, PDF and the + # manual index would be wasted work. + Info( InfoAutoDoc, 1, "Skipping manual generation, extracting examples only" ); + else + CallFuncList( MakeGAPDocDoc, args ); + + # 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; - GAPDocManualLabFromSixFile( gapdoc.bookname, file ); fi; fi; @@ -604,6 +648,16 @@ function( is_worksheet, pkgname, pkginfo, pkgdir, opt ) fi; fi; + if extract_only <> fail and IsBound( gapdoc ) then + # Extracting is the whole point here, so do it even for packages whose + # makedoc.g does not ask for it, and collect the result outside the + # package. Without GAPDoc there is no document to extract from. + if not IsBound( extract_examples ) then + extract_examples := rec( ); + fi; + extract_examples.subdir := Directory( Filename( extract_only, "tst" ) ); + fi; + if IsBound( extract_examples ) then if is_worksheet then # HACK: not even sure this is really what we want for worksheets, but @@ -622,7 +676,15 @@ function( is_worksheet, pkgname, pkginfo, pkgdir, opt ) 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 ); + if extract_only <> fail then + # Sources were staged below the scratch directory, mirroring the + # package layout, so report them relative to it. + extract_roots := [ extract_only ]; + else + extract_roots := [ ]; + fi; + AUTODOC_ExtractMyManualExamples( pkgname, pkgdir, doc_dir, gapdoc.main, + gapdoc.files, extract_examples, extract_roots ); fi; return true; diff --git a/gap/Markdown.gi b/gap/Markdown.gi index 8dfe6d1e..296418e4 100644 --- a/gap/Markdown.gi +++ b/gap/Markdown.gi @@ -107,7 +107,7 @@ BindGlobal( "AUTODOC_ConvertFencedMarkdownBlocks", local converted_source_positions, converted_string_list, i, skipped, string_list, trimmed_line, fence_char, fence_length, info_string, fence_element, code_block, - fence_content, source_positions; + fence_content, fence_node, fence_position, source_positions; string_list := arg[ 1 ]; if Length( arg ) > 1 then @@ -163,9 +163,18 @@ BindGlobal( "AUTODOC_ConvertFencedMarkdownBlocks", Add( fence_content, Chomp( string_list[ i ] ) ); i := i + 1; od; - Add( converted_string_list, - DocumentationVerbatim( fence_element, rec( ), fence_content ) ); - Add( converted_source_positions, source_positions[ i - Length( fence_content ) - 1 ] ); + fence_node := DocumentationVerbatim( fence_element, rec( ), fence_content ); + # The opening fence sits one line above the collected content. + fence_position := source_positions[ i - Length( fence_content ) - 1 ]; + if fence_position <> fail then + fence_node!.source_position := fence_position; + fence_node!.source_end_position := rec( + filename := fence_position.filename, + line := fence_position.line + Length( fence_content ) + 1 + ); + fi; + Add( converted_string_list, fence_node ); + Add( converted_source_positions, fence_position ); if code_block = true then i := i + 1; continue; diff --git a/gap/Parser.gi b/gap/Parser.gi index 7c934001..4dc49046 100644 --- a/gap/Parser.gi +++ b/gap/Parser.gi @@ -762,6 +762,7 @@ InstallGlobalFunction( AutoDoc_Parser_ReadFiles, local temp_string_list, temp_curr_line, temp_pos_comment, is_following_line, item_temp, example_node, end_command; example_node := DocumentationExample( element_name ); + example_node!.source_position := CurrentSourcePosition(); temp_string_list := example_node!.content; end_command := Concatenation( "@End", element_name ); is_following_line := false; @@ -794,6 +795,7 @@ InstallGlobalFunction( AutoDoc_Parser_ReadFiles, continue; fi; od; + example_node!.source_end_position := CurrentSourcePosition(); return example_node; end; ReadSessionExample := function( element_name, plain_text_mode ) @@ -801,6 +803,7 @@ InstallGlobalFunction( AutoDoc_Parser_ReadFiles, is_following_line, item_temp, example_node, incorporate_this_line, end_command; example_node := DocumentationExample( element_name ); + example_node!.source_position := CurrentSourcePosition(); temp_string_list := example_node!.content; end_command := Concatenation( "@End", element_name, "Session" ); while true do @@ -823,6 +826,7 @@ InstallGlobalFunction( AutoDoc_Parser_ReadFiles, Add( temp_string_list, temp_curr_line ); fi; od; + example_node!.source_end_position := CurrentSourcePosition(); return example_node; end; command_function_record := rec( diff --git a/gap/ToolFunctions.gd b/gap/ToolFunctions.gd index 224cca59..a090e033 100644 --- a/gap/ToolFunctions.gd +++ b/gap/ToolFunctions.gd @@ -6,6 +6,11 @@ # SPDX-License-Identifier: GPL-2.0-or-later DeclareGlobalFunction( "AUTODOC_CreateDirIfMissing" ); +DeclareGlobalFunction( "AUTODOC_CommonParentDirectory" ); +DeclareGlobalFunction( "AUTODOC_RelativeSourcePath" ); +DeclareGlobalFunction( "AUTODOC_StageDirectory" ); +DeclareGlobalFunction( "AUTODOC_SourceMarker" ); +DeclareGlobalFunction( "AUTODOC_RemapSourcePositions" ); DeclareGlobalFunction( "AUTODOC_CurrentDirectory" ); DeclareGlobalFunction( "AUTODOC_LineStartsCDATA" ); DeclareGlobalFunction( "AUTODOC_LineEndsCDATA" ); diff --git a/gap/ToolFunctions.gi b/gap/ToolFunctions.gi index 01d0da4b..104a4cc4 100644 --- a/gap/ToolFunctions.gi +++ b/gap/ToolFunctions.gi @@ -553,3 +553,245 @@ function(arg) fi; return result; end); + +# Return the deepest directory containing all of the given paths, as a string +# ending in "/", or fail if they share none. This is derived purely from the +# given strings, so unlike the working directory it is unaffected by where a +# command was started or by symlinks on the way to the files. +InstallGlobalFunction( "AUTODOC_CommonParentDirectory", +function( paths ) + local components, common, i, n; + + if IsEmpty( paths ) then + return fail; + fi; + + # keep the directory components, dropping the file name + components := List( paths, + p -> SplitString( p, "/" ) ); + components := List( components, c -> c{[ 1 .. Length( c ) - 1 ]} ); + + common := components[1]; + for i in [ 2 .. Length( components ) ] do + n := 0; + while n < Length( common ) and n < Length( components[i] ) + and common[ n + 1 ] = components[i][ n + 1 ] do + n := n + 1; + od; + common := common{[ 1 .. n ]}; + od; + + if IsEmpty( common ) then + return fail; + fi; + + return Concatenation( JoinStringsWithSeparator( common, "/" ), "/" ); +end ); + +# Render a source file path for display and for recording in generated files: +# relative to the package directory when it lies below it, else relative to +# the working directory, else the bare filename. Absolute paths would make +# generated output depend on where the package happens to live. +InstallGlobalFunction( "AUTODOC_RelativeSourcePath", +function( path, pkgdir ) + local prefix, candidate; + + for candidate in [ pkgdir, Directory( AUTODOC_CurrentDirectory() ) ] do + prefix := Filename( candidate, "" ); + if prefix <> fail and Length( prefix ) > 1 and StartsWith( path, prefix ) then + return path{ [ Length( prefix ) + 1 .. Length( path ) ] }; + fi; + od; + + return Last( SplitString( path, "/" ) ); +end ); + +# Elements whose content is a manual example, i.e. those GAPDoc's +# ExtractExamplesXMLTree collects. Only these are worth annotating. +BindGlobal( "AUTODOC_EXAMPLE_ELEMENTS", [ "Example", "Log" ] ); + +BindGlobal( "AUTODOC_SOURCE_MARKER_PREFIX", "" ); +BindGlobal( "AUTODOC_CDATA_CLOSE", "]]> +# GAPDoc parses these into XMLCOMMENT nodes which every output backend +# ignores, so they are invisible in the built manual; AutoDoc reads them back +# in AUTODOC_RemapSourcePositions to report the true origin of an example. +# +# Returns fail if the node carries no position, is not an example, or if the +# path cannot be represented in an XML comment. +InstallGlobalFunction( "AUTODOC_SourceMarker", +function( node ) + local position, end_position, text; + + if not IsBound( node!.element_name ) or + not node!.element_name in AUTODOC_EXAMPLE_ELEMENTS or + not IsBound( node!.source_position ) or + node!.source_position = fail then + return fail; + fi; + + position := node!.source_position; + if IsBound( node!.source_end_position ) and node!.source_end_position <> fail then + end_position := node!.source_end_position; + else + end_position := position; + fi; + + # XML forbids "--" inside comments, and ">" would end ours early. Rather + # than mangle the path, drop the marker and fall back to reporting the + # generated XML file, as AutoDoc did before markers existed. + if PositionSublist( position.filename, "--" ) <> fail or + '>' in position.filename then + Info( InfoAutoDoc, 1, "WARNING: cannot record source position for ", + position.filename, ", path is not valid inside an XML comment" ); + return fail; + fi; + + text := Concatenation( + AUTODOC_SOURCE_MARKER_PREFIX, + position.filename, ":", + String( position.line ), "-", String( end_position.line ), + AUTODOC_SOURCE_MARKER_SUFFIX + ); + return text; +end ); + +# Rewrite GAPDoc's origin list in place so that text AutoDoc generated is +# attributed to the file it was generated *from*. +# +# `str` is a composed document as returned by ComposedDocument, and `src` the +# accompanying list of [position, filename, line] triples which +# OriginalPositionDocument searches. Each marker claims the element that +# follows it, up to and including the line closing its CDATA block. Within +# that region we report the recorded start line, except for the closing line, +# which gets the recorded end line. ExtractExamplesXMLTree only ever looks up +# the start and stop of an example, so both of its lookups land exactly. +# +# <- marker at position p +# 1+1; +# 2 +# ]]> <- reported as gap/Foo.gd:141 +InstallGlobalFunction( "AUTODOC_RemapSourcePositions", +function( str, src ) + local marker_start, marker_end, region_start, region_end, colon, dash, + body, filename, start_line, end_line, first, last, i; + + marker_start := PositionSublist( str, AUTODOC_SOURCE_MARKER_PREFIX ); + + while marker_start <> fail do + marker_end := PositionSublist( str, AUTODOC_SOURCE_MARKER_SUFFIX, marker_start ); + if marker_end = fail then + break; + fi; + marker_end := marker_end + Length( AUTODOC_SOURCE_MARKER_SUFFIX ) - 1; + region_start := marker_start; + + body := str{ [ marker_start + Length( AUTODOC_SOURCE_MARKER_PREFIX ) + .. marker_end - Length( AUTODOC_SOURCE_MARKER_SUFFIX ) ] }; + + # Split "path/to/file.gd:137-141" from the right, so that paths + # containing ':' or '-' survive. + colon := Length( body ); + while colon > 0 and body[ colon ] <> ':' do colon := colon - 1; od; + dash := Length( body ); + while dash > colon and body[ dash ] <> '-' do dash := dash - 1; od; + + marker_start := PositionSublist( str, AUTODOC_SOURCE_MARKER_PREFIX, marker_end ); + + if colon = 0 or dash <= colon then + continue; + fi; + + filename := body{ [ 1 .. colon - 1 ] }; + start_line := Int( body{ [ colon + 1 .. dash - 1 ] } ); + end_line := Int( body{ [ dash + 1 .. Length( body ) ] } ); + if start_line = fail or end_line = fail then + continue; + fi; + + # The marker describes exactly one element, which AutoDoc always + # writes CDATA-wrapped, so its closing line is the first one holding + # "]]> fail and region_end > marker_start ) then + continue; + fi; + region_end := Position( str, '\n', region_end ); + if region_end = fail then + region_end := Length( str ); + fi; + + first := PositionSorted( src, [ region_start ] ); + last := PositionSorted( src, [ region_end ] ) - 1; + for i in [ first .. last ] do + if not IsBound( src[ i ] ) then + continue; + fi; + src[ i ][ 2 ] := filename; + if i = last then + src[ i ][ 3 ] := end_line; + else + src[ i ][ 3 ] := start_line; + fi; + od; + od; +end ); + +# Output of a previous manual build. Staging a documentation directory copies +# its inputs only; these are large, regenerated anyway, and never read back. +# Kept in sync with the `clean` target of the Makefile. +BindGlobal( "AUTODOC_BUILD_ARTIFACT_EXTENSIONS", + [ "aux", "bbl", "blg", "brf", "css", "dvi", "html", "idx", "ilg", "ind", + "js", "lab", "log", "out", "pdf", "pnr", "ps", "six", "tex", "toc", + "txt" ] ); + +# Recursively copy the contents of directory `src` into directory `dst`, +# skipping build artifacts. +# +# GAP has no CopyFile, so this goes through StringFile/FileString. Those read +# and write raw bytes, so binary inputs such as images survive. +InstallGlobalFunction( "AUTODOC_StageDirectory", +function( src, dst ) + local entry, entries, source_path, target_path, contents; + + AUTODOC_CreateDirIfMissing( Filename( dst, "" ) ); + + entries := DirectoryContents( src ); + if entries = fail then + # Nothing to stage; a package may not have a doc directory yet. + return; + fi; + + for entry in entries do + if entry = "." or entry = ".." then + continue; + fi; + + source_path := Filename( src, entry ); + + if IsDirectoryPath( source_path ) then + AUTODOC_StageDirectory( Directory( source_path ), + Directory( Filename( dst, entry ) ) ); + continue; + fi; + + if AUTODOC_GetSuffix( entry ) in AUTODOC_BUILD_ARTIFACT_EXTENSIONS then + continue; + fi; + + contents := StringFile( source_path ); + if contents = fail then + continue; + fi; + + target_path := Filename( dst, entry ); + if FileString( target_path, contents ) = fail then + Error( "failed to stage ", source_path, " to ", target_path ); + fi; + od; +end ); diff --git a/init.g b/init.g index ce6f8135..29d314c3 100644 --- a/init.g +++ b/init.g @@ -15,4 +15,6 @@ ReadPackage( "AutoDoc", "gap/ToolFunctions.gd" ); ReadPackage( "AutoDoc", "gap/Magic.gd" ); +ReadPackage( "AutoDoc", "gap/Examples.gd" ); + ReadPackage( "AutoDoc", "gap/Markdown.gd" ); diff --git a/read.g b/read.g index fcc1d7ff..5acaf36d 100644 --- a/read.g +++ b/read.g @@ -15,4 +15,6 @@ ReadPackage( "AutoDoc", "gap/AutoDocMainFunction.gi" ); ReadPackage( "AutoDoc", "gap/Magic.gi" ); +ReadPackage( "AutoDoc", "gap/Examples.gi" ); + ReadPackage( "AutoDoc", "gap/Markdown.gi" ); diff --git a/regen_tests.g b/regen_tests.g index ba938c69..92f0ef14 100644 --- a/regen_tests.g +++ b/regen_tests.g @@ -1,7 +1,6 @@ if fail = LoadPackage("AutoDoc") then Error("failed to load AutoDoc package"); fi; -LoadPackage("io", false); SetInfoLevel(InfoAutoDoc, 1); SetInfoLevel(InfoGAPDoc, 0); diff --git a/tst/autodoctest-manual.tst b/tst/autodoctest-manual.tst index fe5eb8f8..5dc36616 100644 --- a/tst/autodoctest-manual.tst +++ b/tst/autodoctest-manual.tst @@ -8,10 +8,6 @@ gap> START_TEST( "autodoctest-manual.tst" ); -# need IO package for ChangeDirectoryCurrent -gap> LoadPackage("io", false); -true - # temporarily change info levels to suppress all GAPDoc output gap> oldGAPDocLevel := InfoLevel( InfoGAPDoc );; gap> oldWarningLevel := InfoLevel( InfoWarning );; diff --git a/tst/dogfood.tst b/tst/dogfood.tst index 706026ef..090793f3 100644 --- a/tst/dogfood.tst +++ b/tst/dogfood.tst @@ -13,10 +13,6 @@ gap> START_TEST( "dogfood.tst" ); -# need IO package for ChangeDirectoryCurrent -gap> LoadPackage("io", false); -true - # temporarily change info levels to suppress all GAPDoc output gap> oldGAPDocLevel := InfoLevel( InfoGAPDoc );; gap> oldWarningLevel := InfoLevel( InfoWarning );; diff --git a/tst/examples.tst b/tst/examples.tst new file mode 100644 index 00000000..b330345b --- /dev/null +++ b/tst/examples.tst @@ -0,0 +1,228 @@ +# +# test example extraction +# +gap> START_TEST( "examples.tst" ); + +# +# Extracted tests must point at the file the example was written in, not at +# the intermediate XML file AutoDoc generated from it. +# +gap> tmpdir := Filename(DirectoryTemporary(), "autodoc-examples-sources");; +gap> if IsDirectoryPath(tmpdir) then RemoveDirectoryRecursively(tmpdir); fi; +gap> AUTODOC_CreateDirIfMissing(tmpdir); +true +gap> sheetdir := DirectoriesPackageLibrary( +> "AutoDoc", "tst/worksheets/paired-examples.sheet" )[1];; +gap> filenames := DirectoryContents(sheetdir);; +gap> filenames := Filtered(filenames, f -> f <> "." and f <> "..");; +gap> filenames := List(filenames, f -> Filename(sheetdir, f));; +gap> old := InfoLevel(InfoAutoDoc);; oldgapdoc := InfoLevel(InfoGAPDoc);; +gap> SetInfoLevel(InfoAutoDoc, 0); SetInfoLevel(InfoGAPDoc, 0); +gap> AutoDocWorksheet(filenames, +> rec( dir := Directory(tmpdir), extract_examples := true ) : nopdf ); +gap> SetInfoLevel(InfoAutoDoc, old); SetInfoLevel(InfoGAPDoc, oldgapdoc); +gap> lines := SplitString(StringFile( +> Filename(Directory(tmpdir), "tst/paired_examples_test01.tst")), "\n");; + +# The four examples start on lines 8, 13, 18 and 23 of worksheet.g. +gap> Perform(Filtered(lines, +> l -> StartsWith(l, "# ") and PositionSublist(l, ":") <> fail), Display); +# worksheet.g:8-11 +# worksheet.g:13-16 +# worksheet.g:18-21 +# worksheet.g:23-26 + +# No location may name a generated file. +gap> ForAny(lines, l -> PositionSublist(l, "_Chapter_") <> fail); +false +gap> RemoveDirectoryRecursively(tmpdir); +true + +# +# Fenced markdown examples are recorded too, wherever the parser tracks +# source positions for the surrounding text. +# +gap> tmpdir := Filename(DirectoryTemporary(), "autodoc-examples-fence");; +gap> if IsDirectoryPath(tmpdir) then RemoveDirectoryRecursively(tmpdir); fi; +gap> AUTODOC_CreateDirIfMissing(Concatenation(tmpdir, "/src")); +true +gap> source := Concatenation(tmpdir, "/src/fence.g");; +gap> FileString(source, Concatenation( +> "#! @Title Fence Test\n", +> "#! @Date 2026-01-01\n", +> "#! @Chapter Ch\n", +> "#! @Section Sec\n", +> "\n", +> "#! @Description\n", +> "#! Some description text.\n", +> "#! ```@example\n", +> "#! gap> 1+1;\n", +> "#! 2\n", +> "#! ```\n", +> "#! @Arguments x\n", +> "DeclareOperation( \"AutoDocFenceDemo\", [ IsInt ] );\n" )) <> fail; +true +gap> old := InfoLevel(InfoAutoDoc);; oldgapdoc := InfoLevel(InfoGAPDoc);; +gap> SetInfoLevel(InfoAutoDoc, 0); SetInfoLevel(InfoGAPDoc, 0); +gap> AutoDocWorksheet([ source ], +> rec( dir := Directory(Concatenation(tmpdir, "/out")), +> extract_examples := true ) : nopdf ); +gap> SetInfoLevel(InfoAutoDoc, old); SetInfoLevel(InfoGAPDoc, oldgapdoc); +gap> lines := SplitString(StringFile(Filename(Directory(tmpdir), +> "out/tst/fence_test01.tst")), "\n");; + +# The fence opens on line 8 and closes on line 11. +gap> Perform(Filtered(lines, +> l -> StartsWith(l, "# ") and PositionSublist(l, ":") <> fail), Display); +# fence.g:8-11 +gap> RemoveDirectoryRecursively(tmpdir); +true + +# +# AutoDocExtractExamples runs a package's own makedoc.g in extract-only mode: +# no manual is built, and nothing is written into the package directory. +# +gap> pkgdir := DirectoriesPackageLibrary( "AutoDoc", "tst/AutoDocTest" )[1];; +gap> docdir := Directory( Filename( pkgdir, "doc" ) );; +gap> before := Set( DirectoryContents( docdir ) );; +gap> old := InfoLevel(InfoAutoDoc);; oldgapdoc := InfoLevel(InfoGAPDoc);; +gap> SetInfoLevel(InfoAutoDoc, 0); SetInfoLevel(InfoGAPDoc, 0); +gap> tstdir := AutoDocExtractExamples( pkgdir, "makedoc-examples-chapter.g" );; +gap> SetInfoLevel(InfoAutoDoc, old); SetInfoLevel(InfoGAPDoc, oldgapdoc); +gap> IsDirectory( tstdir ); +true + +# The package directory must be untouched: no generated XML, no .tst files. +gap> Set( DirectoryContents( docdir ) ) = before; +true +gap> Filtered( DirectoryContents( Directory( Filename( pkgdir, "tst" ) ) ), +> f -> EndsWith( f, ".tst" ) ); +[ ] + +# The extracted tests match the reference output of a full manual build. +gap> expected := Filename( pkgdir, +> "tst/examples-chapter.expected/autodoctest01.tst" );; +gap> AUTODOC_Diff( "-u", expected, Filename( tstdir, "autodoctest01.tst" ) ); +0 + +# +# Extraction must work when the package directory cannot be written to, as +# happens for packages installed system-wide. +# +gap> frozen := Filename(DirectoryTemporary(), "autodoc-examples-frozen");; +gap> if IsDirectoryPath(frozen) then RemoveDirectoryRecursively(frozen); fi; +gap> Exec(Concatenation("cp -R \"", Filename(pkgdir, ""), "\" \"", frozen, "\"")); +gap> Exec(Concatenation("chmod -R a-w \"", frozen, "\"")); + +# Running as root defeats chmod, so only assert when the dir really is locked. +gap> locked := not IsWritableFile(Concatenation(frozen, "/doc"));; +gap> if locked then +> SetInfoLevel(InfoAutoDoc, 0); SetInfoLevel(InfoGAPDoc, 0); +> tstdir := AutoDocExtractExamples( +> Directory(frozen), "makedoc-examples-chapter.g" ); +> SetInfoLevel(InfoAutoDoc, old); SetInfoLevel(InfoGAPDoc, oldgapdoc); +> if AUTODOC_Diff( "-u", expected, Filename(tstdir, "autodoctest01.tst") ) <> 0 then +> Error("extraction from a read-only package dir gave unexpected output"); +> fi; +> fi; +gap> Exec(Concatenation("chmod -R u+w \"", frozen, "\"")); +gap> RemoveDirectoryRecursively(frozen); +true + +# +# AUTODOC_SourceMarker renders an example's provenance, and declines when it +# cannot: "--" may not appear inside an XML comment. +# +gap> node := DocumentationExample( "Example" );; +gap> node!.source_position := rec( filename := "gap/Foo.gd", line := 5 );; +gap> node!.source_end_position := rec( filename := "gap/Foo.gd", line := 9 );; +gap> AUTODOC_SourceMarker( node ); +"" +gap> listing := DocumentationVerbatim( "Listing", rec( ), [ ] );; +gap> listing!.source_position := node!.source_position;; +gap> AUTODOC_SourceMarker( listing ); +fail +gap> node!.source_position := rec( filename := "gap/a--b.gd", line := 5 );; +gap> SetInfoLevel( InfoAutoDoc, 0 ); +gap> AUTODOC_SourceMarker( node ); +fail +gap> SetInfoLevel( InfoAutoDoc, old ); + +# +# AUTODOC_RemapSourcePositions rewrites GAPDoc's origin list, so that the +# start and end of the example both resolve into the original source. +# +gap> str := Concatenation( +> "\n", +> " "gap> 1+1;\n", +> "2\n", +> "]]>\n" );; +gap> starts := Concatenation( [ 1 ], List( Positions( str, '\n' ), p -> p + 1 ) );; +gap> src := List( [ 1 .. Length( starts ) ], +> i -> [ starts[i], "_Chapter_Generated.xml", i ] );; +gap> AUTODOC_RemapSourcePositions( str, src ); +gap> OriginalPositionDocument( src, PositionSublist( str, " OriginalPositionDocument( src, PositionSublist( str, "]]>" ) ); +[ "gap/Foo.gd", 9 ] + +# Text past the example keeps its own origin. +gap> Last( src ); +[ 82, "_Chapter_Generated.xml", 6 ] + +# +# AUTODOC_CommonParentDirectory anchors recorded paths on the inputs alone. +# +gap> AUTODOC_CommonParentDirectory( [ "/a/b/c.g", "/a/b/d.g" ] ); +"/a/b/" +gap> AUTODOC_CommonParentDirectory( [ "/a/b/c.g", "/a/x/d.g" ] ); +"/a/" +gap> AUTODOC_CommonParentDirectory( [ "/a/b/c.g" ] ); +"/a/b/" + +# Inputs sharing no directory have no anchor, and neither has no input at all. +gap> AUTODOC_CommonParentDirectory( [ "c.g", "d.g" ] ); +fail +gap> AUTODOC_CommonParentDirectory( [ ] ); +fail + +# +# AUTODOC_ExtractOnlyDirectory decides whether to build the manual normally. +# +gap> AUTODOC_ExtractOnlyDirectory(); +fail +gap> AUTODOC_ExtractOnlyDirectory( : AutoDocExtractOnly := false ); +fail +gap> IsDirectory( AUTODOC_ExtractOnlyDirectory( : AutoDocExtractOnly := true ) ); +true +gap> IsDirectory( AUTODOC_ExtractOnlyDirectory( : AutoDocExtractOnly := "1" ) ); +true +gap> scratch := Filename( DirectoryTemporary(), "autodoc-extract-only" );; +gap> AUTODOC_ExtractOnlyDirectory( : AutoDocExtractOnly := scratch ) = +> Directory( scratch ); +true +gap> IsDirectoryPath( scratch ); +true +gap> AUTODOC_ExtractOnlyDirectory( : AutoDocExtractOnly := Directory( scratch ) ) = +> Directory( scratch ); +true +gap> AUTODOC_ExtractOnlyDirectory( : AutoDocExtractOnly := 42 ); +Error, AutoDocExtractOnly must be true, a path, or a directory object + +# An error raised inside a call does not always pop the options stack, which +# would leak the option above into every later test in this session. +gap> if not IsEmpty( OptionsStack ) then ResetOptionsStack(); fi; + +# +# AutoDocExtractExamples rejects what it cannot turn into a package. +# +gap> AutoDocExtractExamples( 42 ); +Error, pkg must be a package name or a directory object +gap> AutoDocExtractExamples( "no-such-package-here" ); +Error, could not locate package no-such-package-here +gap> AutoDocExtractExamples( Directory( "tst" ), "no-such-script.g" ); +Error, could not read tst/no-such-script.g + +# +gap> STOP_TEST( "examples.tst" ); diff --git a/tst/manual.expected/_Chapter_Reference.xml b/tst/manual.expected/_Chapter_Reference.xml index 71c14bc5..0ee14f2b 100644 --- a/tst/manual.expected/_Chapter_Reference.xml +++ b/tst/manual.expected/_Chapter_Reference.xml @@ -22,6 +22,7 @@ A simple worksheet file can define title-page information and chapter content directly in the source file, including example blocks. If this is stored in worksheet.g, you can generate documentation via + @@ -41,6 +42,44 @@ +

+Extracting manual examples + + + + a directory + + + Extracts the examples from the manual of the package pkg and + returns the directory holding the resulting .tst files. +

+ pkg is either the name of a package or a directory object pointing + at one. The optional argument makedoc names the script to read, + and defaults to makedoc.g. +

+ The package's own makedoc.g is used as-is, so the settings which + describe the manual — its source files, scaffolding and + extract_examples options — are not duplicated. Only the parts of + the manual needed to collect the examples are built, no HTML or PDF is + produced, and everything is written to a temporary directory, so this + works even when the package directory is read-only. +

+ This is meant to be used from a package's tst/testall.g, so that + extracted tests need not be committed to the repository: +

+

+ Note that makedoc.g is read in the usual way, so any other work it + performs still happens; and a script ending in QUIT cannot be used + this way. + + + +

+
The AutoDoc() function @@ -443,6 +482,23 @@ Also, if the environment variable NOPDF is set, then &AutoDoc; behaves as if the global option nopdf had been enabled. + AutoDocExtractOnly + + If this global option is set, &AutoDoc; builds only as much of the + manual as is needed to collect its examples: no HTML, PDF or manual + index is produced, and all output is written below the given scratch + directory instead of the package. The extracted tests end up in its + tst subdirectory. +

+ The value is a directory, or true to have one chosen automatically. + If the environment variable AUTODOC_EXTRACT_ONLY is set, &AutoDoc; + behaves as if this option had been given; that is useful for a + makedoc.g which ends in QUIT and therefore has to be run + as a separate process. +

+ Rather than setting this by hand, use + . + relativePath This has the same effect as gapdoc.gap_root_relative_path, but diff --git a/tst/manual.expected/_Chapter_Tutorials.xml b/tst/manual.expected/_Chapter_Tutorials.xml index ec3bb577..bd782c1b 100644 --- a/tst/manual.expected/_Chapter_Tutorials.xml +++ b/tst/manual.expected/_Chapter_Tutorials.xml @@ -291,6 +291,21 @@ AutoDoc( rec( extract_examples := rec( subdir := "tst/generated" ) ) ); ]]> This writes the extracted examples into tst/generated/ instead.

+Either way the generated .tst files have to be regenerated whenever the +manual changes, which tempts one into committing them. To avoid that, extract +them when the tests run, using in your +tst/testall.g: +

+This reads your makedoc.g, so the settings describing the manual are not +duplicated, but skips building the manual itself. Everything is written to a +temporary directory, so the extracted tests need not be committed and the tests +also run from a read-only package directory. +

diff --git a/tst/misc.tst b/tst/misc.tst index b9be8170..5e86aa3a 100644 --- a/tst/misc.tst +++ b/tst/misc.tst @@ -109,8 +109,6 @@ gap> Scan_for_AutoDoc_Part( "### Heading subsection" ); # # AUTODOC_CreateDirIfMissing: nested paths and `..` normalization # -gap> LoadPackage("io", false); -true gap> tmpdir := Filename(DirectoryTemporary(), "autodoc-createdir-test");; gap> if IsDirectoryPath(tmpdir) then RemoveDirectoryRecursively(tmpdir); fi; gap> AUTODOC_CreateDirIfMissing(tmpdir); diff --git a/tst/worksheets/autoplain.expected/_Chapter_Test.xml b/tst/worksheets/autoplain.expected/_Chapter_Test.xml index becd3a98..9e1df079 100644 --- a/tst/worksheets/autoplain.expected/_Chapter_Test.xml +++ b/tst/worksheets/autoplain.expected/_Chapter_Test.xml @@ -12,6 +12,7 @@ the documentation generated from a .autodoc file like this? First Subsection + S5 := SymmetricGroup(5); Sym( [ 1 .. 5 ] ) @@ -25,6 +26,7 @@ Some text between two examples ampersand key less-than key indented command parsing works + A5 := AlternatingGroup(5); Alt( [ 1 .. 5 ] ) @@ -32,6 +34,7 @@ gap> Size(A5); 60 ]]> + plain_mode_value := 6 * > 9; diff --git a/tst/worksheets/autoplain.expected/tst/plain_file.autodoc_test01.tst b/tst/worksheets/autoplain.expected/tst/plain_file.autodoc_test01.tst index ce9ed3f3..9e5f2b98 100644 --- a/tst/worksheets/autoplain.expected/tst/plain_file.autodoc_test01.tst +++ b/tst/worksheets/autoplain.expected/tst/plain_file.autodoc_test01.tst @@ -10,19 +10,19 @@ # gap> START_TEST("plain_file.autodoc_test01.tst"); -# _Chapter_Test.xml:15-20 +# plain.autodoc:9-14 gap> S5 := SymmetricGroup(5); Sym( [ 1 .. 5 ] ) gap> Size(S5); 120 -# _Chapter_Test.xml:28-33 +# plain.autodoc:21-26 gap> A5 := AlternatingGroup(5); Alt( [ 1 .. 5 ] ) gap> Size(A5); 60 -# _Chapter_Test.xml:35-39 +# plain.autodoc:27-31 gap> plain_mode_value := 6 * > 9; 54 diff --git a/tst/worksheets/general.expected/_Chapter_SomeChapter.xml b/tst/worksheets/general.expected/_Chapter_SomeChapter.xml index 6366f069..09b72423 100644 --- a/tst/worksheets/general.expected/_Chapter_SomeChapter.xml +++ b/tst/worksheets/general.expected/_Chapter_SomeChapter.xml @@ -5,6 +5,7 @@ SomeChapter This is dummy text + S5 := SymmetricGroup(5); Sym( [ 1 .. 5 ] ) @@ -17,6 +18,7 @@ gap> Size(S5); greater-than key quote key + A5 := AlternatingGroup(5); Alt( [ 1 .. 5 ] ) @@ -27,6 +29,7 @@ gap> [[2]]]]>[[1]]; true ]]> + comment_mode_value := 6 * > 7; diff --git a/tst/worksheets/general.expected/tst/general_test01.tst b/tst/worksheets/general.expected/tst/general_test01.tst index 2d7a8568..a9c03eca 100644 --- a/tst/worksheets/general.expected/tst/general_test01.tst +++ b/tst/worksheets/general.expected/tst/general_test01.tst @@ -10,13 +10,13 @@ # gap> START_TEST("general_test01.tst"); -# _Chapter_SomeChapter.xml:8-13 +# worksheet.g:13-18 gap> S5 := SymmetricGroup(5); Sym( [ 1 .. 5 ] ) gap> Size(S5); 120 -# _Chapter_SomeChapter.xml:20-28 +# worksheet.g:24-32 gap> A5 := AlternatingGroup(5); Alt( [ 1 .. 5 ] ) gap> Size(A5); @@ -25,7 +25,7 @@ gap> # Test whether ]]> can be used safely gap> [[2]]>[[1]]; true -# _Chapter_SomeChapter.xml:30-34 +# worksheet.g:33-37 gap> comment_mode_value := 6 * > 7; 42 diff --git a/tst/worksheets/paired-examples-autoplain.expected/_Chapter_Examples_Chapter.xml b/tst/worksheets/paired-examples-autoplain.expected/_Chapter_Examples_Chapter.xml index 5cf8a1bd..d66cd78e 100644 --- a/tst/worksheets/paired-examples-autoplain.expected/_Chapter_Examples_Chapter.xml +++ b/tst/worksheets/paired-examples-autoplain.expected/_Chapter_Examples_Chapter.xml @@ -12,24 +12,28 @@ This worksheet exercises example and log commands in plain-text mode. Tested examples + plain_example_value := 2 + 3; 5 ]]>

+ plain_alias_example := 3 + 4; 7 ]]>

+ 10 - 3; 7 ]]>

+ 6 * 7; 42 @@ -41,24 +45,28 @@ gap> 6 * 7; Untested logs + plain_log_value := 9; 9 ]]>

+ plain_alias_log := 11; 11 ]]>

+ "plain log session"; "plain log session" ]]>

+ "plain alias log session"; "plain alias log session" diff --git a/tst/worksheets/paired-examples-autoplain.expected/tst/paired_examples_test01.tst b/tst/worksheets/paired-examples-autoplain.expected/tst/paired_examples_test01.tst index 9153b459..4e2fa7ba 100644 --- a/tst/worksheets/paired-examples-autoplain.expected/tst/paired_examples_test01.tst +++ b/tst/worksheets/paired-examples-autoplain.expected/tst/paired_examples_test01.tst @@ -10,19 +10,19 @@ # gap> START_TEST("paired_examples_test01.tst"); -# _Chapter_Examples_Chapter.xml:15-18 +# plain.autodoc:8-11 gap> plain_example_value := 2 + 3; 5 -# _Chapter_Examples_Chapter.xml:21-24 +# plain.autodoc:13-16 gap> plain_alias_example := 3 + 4; 7 -# _Chapter_Examples_Chapter.xml:27-30 +# plain.autodoc:18-21 gap> 10 - 3; 7 -# _Chapter_Examples_Chapter.xml:33-36 +# plain.autodoc:23-26 gap> 6 * 7; 42 diff --git a/tst/worksheets/paired-examples.expected/_Chapter_Examples_Chapter.xml b/tst/worksheets/paired-examples.expected/_Chapter_Examples_Chapter.xml index 89dd9a62..d7ef722d 100644 --- a/tst/worksheets/paired-examples.expected/_Chapter_Examples_Chapter.xml +++ b/tst/worksheets/paired-examples.expected/_Chapter_Examples_Chapter.xml @@ -11,21 +11,25 @@ Tested examples + comment_example_value := 2 + 3; 5 ]]> + comment_alias_example := 3 + 4; 7 ]]> + 10 - 3; 7 ]]> + 6 * 7; 42 @@ -36,21 +40,25 @@ gap> 6 * 7; Untested logs + comment_log_value := 9; 9 ]]> + comment_alias_log := 11; 11 ]]> + "comment log session"; "comment log session" ]]> + "comment alias log session"; "comment alias log session" diff --git a/tst/worksheets/paired-examples.expected/tst/paired_examples_test01.tst b/tst/worksheets/paired-examples.expected/tst/paired_examples_test01.tst index 08a5f4aa..fd4b86e0 100644 --- a/tst/worksheets/paired-examples.expected/tst/paired_examples_test01.tst +++ b/tst/worksheets/paired-examples.expected/tst/paired_examples_test01.tst @@ -10,19 +10,19 @@ # gap> START_TEST("paired_examples_test01.tst"); -# _Chapter_Examples_Chapter.xml:14-17 +# worksheet.g:8-11 gap> comment_example_value := 2 + 3; 5 -# _Chapter_Examples_Chapter.xml:19-22 +# worksheet.g:13-16 gap> comment_alias_example := 3 + 4; 7 -# _Chapter_Examples_Chapter.xml:24-27 +# worksheet.g:18-21 gap> 10 - 3; 7 -# _Chapter_Examples_Chapter.xml:29-32 +# worksheet.g:23-26 gap> 6 * 7; 42