diff --git a/.codespellrc b/.codespellrc index 92e8c49..ff818f3 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,3 +1,3 @@ [codespell] -skip = ./.git,./doc/*.log,./doc/*.html,./doc/*.txt,./doc/*.six,./doc/*.js,./doc/*.bbl,./doc/*.tex,./doc/*.bib,./doc/_* -ignore-words-list=fille +skip = ./.git,./doc/*.log,./doc/*.html,./doc/*.txt,./doc/*.six,./doc/*.js,./doc/*.bbl,./doc/*.tex,./doc/*.bib,./doc/_*,./tst/* +ignore-words-list=manuel diff --git a/TODO.md b/TODO.md index 8ba8f71..5165e96 100644 --- a/TODO.md +++ b/TODO.md @@ -8,11 +8,11 @@ - make it so when duplicates are added to global graph attrs the old values are automatically replaced - Improve behaviour around ':' syntax - mimic python package -## TODO (ONCE THIS IS EMPTY THEN DONE!) - - Ask about `Pluralize` - think james said to remove for old versions of gap +## TODO - Update docs - Add more unit tests - Thoroughly test the ':' syntax more (might have broke when the quotes were changed) + - PrintObj method is missing for nodes (and probably edges) ## Other - relates to deadnaut github issue https://www.mankier.com/1/nauty-dretodot diff --git a/etc/code-coverage-test-gap.py b/etc/code-coverage-test-gap.py new file mode 100755 index 0000000..c9b0432 --- /dev/null +++ b/etc/code-coverage-test-gap.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +This is a simple script to run code coverage for some test files. +""" +# pylint: disable=invalid-name + +import argparse +import os +import re +import subprocess +import sys +import tempfile + +from os.path import exists, isdir, isfile +from os import getcwd + +_ERR_PREFIX = "\033[31mcode-coverage-test-gap.py: error: " +_INFO_PREFIX = "\033[0m\033[1m" + +_PARSER = argparse.ArgumentParser( + prog="code-coverage-test-gap.py", usage="%(prog)s [options]" +) +_PARSER.add_argument( + "tstfiles", + nargs="+", + type=str, + help="the test files you want to check code coverage for" + + "(must be at least one)", +) +_PARSER.add_argument( + "--gap-root", + nargs="?", + type=str, + help="the gap root directory (default: ~/gap)", + default="~/gap/", +) +_PARSER.add_argument( + "--open", + nargs="?", + type=str, + help=("open the html page for this file (default: None)"), + default=None, +) + +_ARGS = _PARSER.parse_args() +if not _ARGS.gap_root[-1] == "/": + _ARGS.gap_root += "/" + +if exists("gap") and isdir("gap"): + _PROFILE_DIR = "/gap/" +elif exists("lib") and isdir("lib"): + _PROFILE_DIR = "/lib/" +else: + sys.exit(f"{_ERR_PREFIX}no directory gap or lib to profile!\033[0m") + +_ARGS.gap_root = os.path.expanduser(_ARGS.gap_root) +if not (exists(_ARGS.gap_root) and isdir(_ARGS.gap_root)): + sys.exit(f"{_ERR_PREFIX}can't find GAP root directory!\033[0m") + +for f in _ARGS.tstfiles: + if not (exists(f) and isfile(f)): + sys.exit(f"{_ERR_PREFIX}{f} does not exist!\033[0m") + +_DIR = tempfile.mkdtemp() +print(f"{_INFO_PREFIX}Using temporary directory: {_DIR}\033[0m") + +_COMMANDS = 'echo "' +_COMMANDS += "".join(rf"Test(\"{f}\");;\n" for f in _ARGS.tstfiles) +_COMMANDS += rf"""UncoverageLineByLine();; +LoadPackage(\"profiling\", false);; +filesdir := \"{getcwd()}{_PROFILE_DIR}\";;\n""" + +_COMMANDS += rf"outdir := \"{_DIR}\";;\n" +_COMMANDS += rf"x := ReadLineByLineProfile(\"{_DIR}/profile.gz\");;\n" +_COMMANDS += 'OutputAnnotatedCodeCoverageFiles(x, filesdir, outdir);"' + +_RUN_GAP = f"{_ARGS.gap_root}/gap -A -m 1g -T --cover {_DIR}/profile.gz" + +with subprocess.Popen(_COMMANDS, stdout=subprocess.PIPE, shell=True) as pro1: + try: + with subprocess.Popen(_RUN_GAP, stdin=pro1.stdout, shell=True) as pro2: + pro2.wait() + except KeyboardInterrupt: + pro1.terminate() + pro1.wait() + sys.exit("\033[31mKilled!\033[0m") + except (subprocess.CalledProcessError, IOError, OSError): + sys.exit(_ERR_PREFIX + "Something went wrong calling GAP!\033[0m") + + +def rewrite_fname(fname: str) -> str: + return fname.replace("/", "_") + + +suffix = "" +if _ARGS.open: + filename = f"{_DIR}/{rewrite_fname(getcwd())}/{rewrite_fname(_ARGS.open)}.html" + p = re.compile(r"") + with open(filename, "r", encoding="utf-8") as f: + m = p.search(f.read()) + if m: + suffix += "#line" + m.group(1) +else: + filename = _DIR + "/index.html" +print(f"{_INFO_PREFIX}\nSUCCESS!\033[0m") +print(f"{_INFO_PREFIX}See {filename}") +sys.exit(0) diff --git a/etc/tst-local-vars.yml b/etc/tst-local-vars.yml new file mode 100644 index 0000000..844de31 --- /dev/null +++ b/etc/tst-local-vars.yml @@ -0,0 +1,8 @@ +name: tst-local-vars + +channels: + - conda-forge + +dependencies: + - pyyaml + - bs4 diff --git a/gap/dot.gd b/gap/dot.gd index 3921015..9ef407e 100644 --- a/gap/dot.gd +++ b/gap/dot.gd @@ -30,19 +30,22 @@ #! @Section Graphviz Categories #! @BeginGroup Filters -#! @Description Every object in graphviz belongs to the IsGVObject category. -#! The categories following it are for further specificity on the type of -#! objects. These are graphs, digraphs, nodes and edges respectively. -#! All are direct subcategories of IsGVObject excluding IsGVDigraph which is a -#! subcategory of is GVGraph. -DeclareCategory("IsGVObject", IsObject); -DeclareCategory("IsGVGraph", IsGVObject); -# TODO change to IsGVObject below, since digraphs aren't a special kind of -# graph, unless I'm (JDM) mistaken? -DeclareCategory("IsGVDigraph", IsGVGraph); -DeclareCategory("IsGVContext", IsGVGraph); -DeclareCategory("IsGVNode", IsGVObject); -DeclareCategory("IsGVEdge", IsGVObject); +#! @Description Every object in graphviz belongs to the IsGraphvizObject +#! category. The categories following it are for further specificity on the +#! type of objects. These are graphs, digraphs, nodes and edges respectively. +#! All are direct subcategories of IsGraphvizObject excluding IsGraphvizDigraph +#! which is a subcategory of is IsGraphvizGraph. + +DeclareCategory("IsGraphvizObject", IsObject); + +DeclareCategory("IsGraphvizGraphDigraphOrContext", IsGraphvizObject); +DeclareCategory("IsGraphvizGraph", IsGraphvizGraphDigraphOrContext); +DeclareCategory("IsGraphvizDigraph", IsGraphvizGraphDigraphOrContext); +DeclareCategory("IsGraphvizContext", IsGraphvizGraphDigraphOrContext); + +DeclareCategory("IsGraphvizNodeOrEdge", IsGraphvizObject); +DeclareCategory("IsGraphvizNode", IsGraphvizNodeOrEdge); +DeclareCategory("IsGraphvizEdge", IsGraphvizNodeOrEdge); #! @EndGroup #! @Section Constructors @@ -74,27 +77,32 @@ DeclareOperation("GraphvizDigraph", []); #! @Arguments obj #! @Returns the name of the provided graphviz object #! @Description Gets the name of the provided graphviz object. -DeclareOperation("GraphvizName", [IsGVObject]); +DeclareOperation("GraphvizName", [IsGraphvizObject]); #! @Arguments obj #! @Returns the attributes of the provided graphviz object #! @Description Gets the attributes of the provided graphviz object. -DeclareOperation("GraphvizAttrs", [IsGVObject]); +DeclareOperation("GraphvizAttrs", [IsGraphvizObject]); #! @Subsection For only graphs and digraphs. #! @Arguments graph #! @Returns the nodes of the provided graphviz graph. #! @Description Gets the nodes of the provided graphviz graph. -#! Node names can only be [a-zA-Z0-9_£] TODO check exact docs. -DeclareOperation("GraphvizNodes", [IsGVGraph]); -DeclareOperation("GraphvizNode", [IsGVGraph, IsObject]); +# From https://graphviz.org/doc/info/lang.html +# An ID is one of the following: +# Any string of alphabetic ([a-zA-Z\200-\377]) characters, underscores ('_') or +# digits([0-9]), not beginning with a digit; +# a numeral [-]?(.[0-9]⁺ | [0-9]⁺(.[0-9]*)? ); +# any double-quoted string ("...") possibly containing escaped quotes (\")¹; +# an HTML string (<...>). +# TODO specify +DeclareOperation("GraphvizNodes", [IsGraphvizGraphDigraphOrContext]); #! @Arguments graph #! @Returns the subgraphs of the provided graphviz graph. #! @Description gets the subgraphs of a provided graphviz graph. -DeclareOperation("GraphvizSubgraphs", [IsGVGraph]); -DeclareOperation("GraphvizGetSubgraph", [IsGVGraph, IsObject]); +DeclareOperation("GraphvizSubgraphs", [IsGraphvizGraphDigraphOrContext]); #! @Arguments graph, name #! @Returns a graph with the provided name. @@ -102,26 +110,28 @@ DeclareOperation("GraphvizGetSubgraph", [IsGVGraph, IsObject]); #! Searches through the tree of subgraphs connected to this subgraph for a graph #! with the provided name. #! It returns the graph if it exists. -#! If no such graph exists then it will return fail. -DeclareOperation("GraphvizFindGraph", [IsGVGraph, IsObject]); +#! If no such graph exists then it will return fail. +DeclareOperation("GraphvizFindSubgraphRecursive", +[IsGraphvizGraphDigraphOrContext, IsObject]); #! @Arguments graph #! @Returns the edges of the provided graphviz graph. #! @Description Gets the edges of the provided graphviz graph. -DeclareOperation("GraphvizEdges", [IsGVGraph]); -DeclareOperation("GraphvizEdges", [IsGVGraph, IsObject, IsObject]); +DeclareOperation("GraphvizEdges", [IsGraphvizGraphDigraphOrContext]); +DeclareOperation("GraphvizEdges", +[IsGraphvizGraphDigraphOrContext, IsObject, IsObject]); #! @Subsection For only edges. #! @Arguments edge #! @Returns the head of the provided graphviz edge. #! @Description Gets the head of the provided graphviz graph. -DeclareOperation("GraphvizHead", [IsGVEdge]); +DeclareOperation("GraphvizHead", [IsGraphvizEdge]); #! @Arguments edge #! @Returns the head of the provided graphviz tail. #! @Description Gets the tail of the provided graphviz graph. -DeclareOperation("GraphvizTail", [IsGVEdge]); +DeclareOperation("GraphvizTail", [IsGraphvizEdge]); #! @Section Set Operations #! This section covers operations for modifying graphviz objects. @@ -131,13 +141,13 @@ DeclareOperation("GraphvizTail", [IsGVEdge]); #! @Arguments graph, name #! @Returns the modified graph. #! @Description Sets the name of a graphviz graph or digraph. -DeclareOperation("GraphvizSetName", [IsGVGraph, IsObject]); +DeclareOperation("GraphvizSetName", [IsGraphvizGraphDigraphOrContext, IsObject]); #! @Arguments graph, node #! @Returns the modified graph. #! @Description Adds a node to the graph. #! If a node with the same name is already present the operation fails. -DeclareOperation("GraphvizAddNode", [IsGVGraph, IsObject]); +DeclareOperation("GraphvizAddNode", [IsGraphvizGraphDigraphOrContext, IsObject]); #! @Arguments graph, edge #! @Returns the modified graph. @@ -145,35 +155,41 @@ DeclareOperation("GraphvizAddNode", [IsGVGraph, IsObject]); #! If no nodes with the same name are in the graph then the edge's nodes will be #! added to the graph. If different nodes with the same name are in the graph #! then the operation fails. -DeclareOperation("GraphvizAddEdge", [IsGVGraph, IsObject, IsObject]); +DeclareOperation("GraphvizAddEdge", +[IsGraphvizGraphDigraphOrContext, IsObject, IsObject]); #! @Arguments graph, filter, name #! @Returns the new subgraph. #! @Description Adds a subgraph to a graph. -DeclareOperation("GraphvizAddSubgraph", [IsGVGraph, IsObject]); -DeclareOperation("GraphvizAddSubgraph", [IsGVGraph]); +DeclareOperation("GraphvizAddSubgraph", +[IsGraphvizGraphDigraphOrContext, IsObject]); +DeclareOperation("GraphvizAddSubgraph", [IsGraphvizGraphDigraphOrContext]); #! @Arguments graph, filter, name #! @Returns the new context. #! @Description Adds a context to a graph. -DeclareOperation("GraphvizAddContext", [IsGVGraph, IsObject]); -DeclareOperation("GraphvizAddContext", [IsGVGraph]); +DeclareOperation("GraphvizAddContext", +[IsGraphvizGraphDigraphOrContext, IsObject]); +DeclareOperation("GraphvizAddContext", [IsGraphvizGraphDigraphOrContext]); #! @Arguments graph, node #! @Returns the modified graph. #! @Description Removes the node from the graph. -DeclareOperation("GraphvizRemoveNode", [IsGVGraph, IsObject]); +DeclareOperation("GraphvizRemoveNode", +[IsGraphvizGraphDigraphOrContext, IsObject]); #! @Arguments graph, predicate #! @Returns the modified graph. #! @Description Filters the graph's edges using the provided predicate. -DeclareOperation("GraphvizFilterEdges", [IsGVGraph, IsFunction]); +DeclareOperation("GraphvizFilterEdges", +[IsGraphvizGraphDigraphOrContext, IsFunction]); #! @Arguments graph, head_name, tail_name #! @Returns the modified graph. #! @Description Filters the graph's edges, removing edges between nodes with #! the specified names. -DeclareOperation("GraphvizFilterEnds", [IsGVGraph, IsObject, IsObject]); +DeclareOperation("GraphvizRemoveEdges", +[IsGraphvizGraphDigraphOrContext, IsObject, IsObject]); #! @Subsection For modifying object attributes. @@ -184,37 +200,19 @@ DeclareOperation("GraphvizFilterEnds", [IsGVGraph, IsObject, IsObject]); #! All current attributes remain. #! If an attribute already exists and a new value is provided, the old value #! will be overwritten. -DeclareOperation("GraphvizSetAttrs", [IsGVObject, IsRecord]); -DeclareOperation("GraphvizSetAttr", [IsGVObject, IsObject, IsObject]); -DeclareOperation("GraphvizSetAttr", [IsGVObject, IsObject]); - -#! @Arguments obj, label -#! @Returns the modified object. -#! @Description -#! Updates the label of the object. -#! If a label already exists and a new value is provided, the old value will -#! be overwritten. -# TODO remove -DeclareOperation("GraphvizSetLabel", [IsGVObject, IsObject]); - -#! @Arguments obj, color -#! @Returns the modified object. -#! @Description -#! Updates the color of the object. -#! If a color already exists and a new value is provided, the old value will -#! be overwritten. -# TODO remove -DeclareOperation("GraphvizSetColor", [IsGVObject, IsObject]); +DeclareOperation("GraphvizSetAttrs", [IsGraphvizObject, IsRecord]); +DeclareOperation("GraphvizSetAttr", [IsGraphvizObject, IsObject, IsObject]); +DeclareOperation("GraphvizSetAttr", [IsGraphvizObject, IsObject]); #! @Arguments obj, attr #! @Returns the modified object. #! @Description Removes an attribute from the object provided. -DeclareOperation("GraphvizRemoveAttr", [IsGVObject, IsObject]); +DeclareOperation("GraphvizRemoveAttr", [IsGraphvizObject, IsObject]); #! @Section Outputting #! @Arguments graph #! @Returns the dot representation of the graphviz object. -DeclareOperation("AsString", [IsGVGraph]); +DeclareOperation("AsString", [IsGraphvizGraphDigraphOrContext]); #! @Arguments obj #! @Returns the graphviz representation of the object. @@ -223,6 +221,22 @@ DeclareOperation("AsString", [IsGVGraph]); #! Should output the graphviz package representation of the object. DeclareOperation("Graphviz", [IsObject]); -DeclareOperation("GraphvizSetNodeColors", [IsGVGraph, IsList]); -DeclareOperation("GraphvizSetNodeLabels", [IsGVGraph, IsList]); -DeclareGlobalFunction("ErrorFormatted"); +DeclareOperation("GraphvizSetNodeColors", +[IsGraphvizGraphDigraphOrContext, IsList]); +DeclareOperation("GraphvizSetNodeLabels", +[IsGraphvizGraphDigraphOrContext, IsList]); + +DeclareGlobalFunction("ErrorIfNotValidColor"); + +# TODO doc +DeclareOperation("\[\]", [IsGraphvizNode, IsObject]); +# TODO doc +DeclareOperation("\[\]\:\=", [IsGraphvizNode, IsObject, IsObject]); + +# TODO doc +DeclareOperation("\[\]", [IsGraphvizEdge, IsObject]); +# TODO doc +DeclareOperation("\[\]\:\=", [IsGraphvizEdge, IsObject, IsObject]); + +# TODO doc +DeclareOperation("\[\]", [IsGraphvizGraphDigraphOrContext, IsObject]); diff --git a/gap/dot.gi b/gap/dot.gi index 2dfa745..ab34369 100644 --- a/gap/dot.gi +++ b/gap/dot.gi @@ -9,438 +9,10 @@ ## ############################################################################# -# Private functionality +# Constructors ############################################################################# -BindGlobal("NumberOfSubstrings", -function(string, substring) - local pos, count; - - pos := 0; - count := 0; - while pos <= Length(string) and pos <> fail do - pos := PositionSublist(string, substring, pos); - if pos <> fail then - count := count + 1; - fi; - od; - return count; -end); - -InstallGlobalFunction(ErrorFormatted, -function(arg...) - local pos, fmt, n, msg; - - pos := PositionProperty(arg, x -> not IsString(x)); - if pos = fail then - pos := Length(arg) + 1; - fi; - fmt := Concatenation(arg{[1 .. pos - 1]}); - n := NumberOfSubstrings(fmt, "{}"); - arg := Concatenation([Concatenation(arg{[1 .. Length(arg) - n]})], - arg{[Length(arg) - n + 1 .. Length(arg)]}); - msg := CallFuncList(StringFormatted, arg); - # RemoveCharacters(msg, "\\\n"); - ErrorInner( - rec(context := ParentLVars(GetCurrentLVars()), - mayReturnVoid := false, - mayReturnObj := false, - lateMessage := "type 'quit;' to quit to outer loop", - printThisStatement := false), - [msg]); -end); - -DeclareOperation("GV_GetCounter", [IsGVGraph]); -DeclareOperation("GV_IncCounter", [IsGVGraph]); -DeclareCategory("IsGV_Map", IsObject); -DeclareAttribute("Size", IsGV_Map); - -DeclareOperation("GV_StringifyGraphHead", [IsGVGraph]); -DeclareOperation("GV_StringifyDigraphHead", [IsGVGraph]); -DeclareOperation("GV_StringifySubgraphHead", [IsGVGraph]); -DeclareOperation("GV_StringifyContextHead", [IsGVGraph]); -DeclareOperation("GV_StringifyNode", [IsGVNode]); -DeclareOperation("GV_StringifyGraphAttrs", [IsGVGraph]); -DeclareOperation("GV_StringifyNodeEdgeAttrs", [IsGV_Map]); -DeclareOperation("GV_StringifyGraph", [IsGVGraph, IsBool]); - -DeclareOperation("GV_FindNode", [IsGVGraph, IsObject]); - -## COPY OF GAP PLURALIZE TO ALLOW OLD VERSIONS OF GAP TO USE THE PACKAGE -DeclareOperation("GV_Pluralize", [IsInt, IsString]); - -BindGlobal("GV_KNOWN_ATTRS", [ - "_background", "area", "arrowhead", "arrowsize", "arrowtail", "bb", - "beautify", "bgcolor", "center", "charset", "class", "cluster", "clusterrank", - "color", "colorscheme", "comment", "compound", "concentrate", "constraint", - "Damping", "decorate", "defaultdist", "dim", "dimen", "dir", - "diredgeconstraints", "distortion", "dpi", "edgehref", "edgetarget", - "edgetooltip", "edgeURL", "epsilon", "esep", "fillcolor", "fixedsize", - "fontcolor", "fontname", "fontnames", "fontpath", "fontsize", "forcelabels", - "gradientangle", "group", "head_lp", "headclip", "headhref", "headlabel", - "headport", "headtarget", "headtooltip", "headURL", "height", "href", "id", - "image", "imagepath", "imagepos", "imagescale", "inputscale", "K", "label", - "label_scheme", "labelangle", "labeldistance", "labelfloat", "labelfontcolor", - "labelfontname", "labelfontsize", "labelhref", "labeljust", "labelloc", - "labeltarget", "labeltooltip", "labelURL", "landscape", "layer", - "layerlistsep", "layers", "layerselect", "layersep", "layout", "len", - "levels", "levelsgap", "lhead", "lheight", "linelength", "lp", "ltail", - "lwidth", "margin", "maxiter", "mclimit", "mindist", "minlen", "mode", - "model", "newrank", "nodesep", "nojustify", "normalize", "notranslate", - "nslimit", "nslimit1", "oneblock", "ordering", "orientation", "outputorder", - "overlap", "overlap_scaling", "overlap_shrink", "pack", "packmode", "pad", - "page", "pagedir", "pencolor", "penwidth", "peripheries", "pin", "pos", - "quadtree", "quantum", "rank", "rankdir", "ranksep", "ratio", "rects", - "regular", "remincross", "repulsiveforce", "resolution", "root", "rotate", - "rotation", "samehead", "sametail", "samplepoints", "scale", "searchsize", - "sep", "shape", "shapefile", "showboxes", "sides", "size", "skew", - "smoothing", "sortv", "splines", "start", "style", "stylesheet", "tail_lp", - "tailclip", "tailhref", "taillabel", "tailport", "tailtarget", "tailtooltip", - "tailURL", "target", "TBbalance", "tooltip", "truecolor", "URL", "vertices", - "viewport", "voro_margin", "weight", "width", "xdotversion", "xlabel", "xlp", - "z" -]); - -BindGlobal("GV_ValidColorNames", - ["aliceblue", "antiquewhite", "antiquewhite1", "antiquewhite2", - "antiquewhite3", "antiquewhite4", "aquamarine", "aquamarine1", "aquamarine2", - "aquamarine3", "aquamarine4", "azure", "azure1", "azure2", "azure3", - "azure4", "beige", "bisque", "bisque1", "bisque2", "bisque3", "bisque4", - "black", "blanchedalmond", "blue", "blue1", "blue2", "blue3", "blue4", - "blueviolet", "brown", "brown1", "brown2", "brown3", "brown4", "burlywood", - "burlywood1", "burlywood2", "burlywood3", "burlywood4", "cadetblue", - "cadetblue1", "cadetblue2", "cadetblue3", "cadetblue4", "chartreuse", - "chartreuse1", "chartreuse2", "chartreuse3", "chartreuse4", "chocolate", - "chocolate1", "chocolate2", "chocolate3", "chocolate4", "coral", "coral1", - "coral2", "coral3", "coral4", "cornflowerblue", "cornsilk", "cornsilk1", - "cornsilk2", "cornsilk3", "cornsilk4", "crimson", "cyan", "cyan1", "cyan2", - "cyan3", "cyan4", "darkgoldenrod", "darkgoldenrod1", "darkgoldenrod2", - "darkgoldenrod3", "darkgoldenrod4", "darkgreen", "darkkhaki", - "darkolivegreen", "darkolivegreen1", "darkolivegreen2", "darkolivegreen3", - "darkolivegreen4", "darkorange", "darkorange1", "darkorange2", "darkorange3", - "darkorange4", "darkorchid", "darkorchid1", "darkorchid2", "darkorchid3", - "darkorchid4", "darksalmon", "darkseagreen", "darkseagreen1", - "darkseagreen2", "darkseagreen3", "darkseagreen4", "darkslateblue", - "darkslategray", "darkslategray1", "darkslategray2", "darkslategray3", - "darkslategray4", "darkslategrey", "darkturquoise", "darkviolet", "deeppink", - "deeppink1", "deeppink2", "deeppink3", "deeppink4", "deepskyblue", - "deepskyblue1", "deepskyblue2", "deepskyblue3", "deepskyblue4", "dimgray", - "dimgrey", "dodgerblue", "dodgerblue1", "dodgerblue2", "dodgerblue3", - "dodgerblue4", "firebrick", "firebrick1", "firebrick2", "firebrick3", - "firebrick4", "floralwhite", "forestgreen", "gainsboro", "ghostwhite", - "gold", "gold1", "gold2", "gold3", "gold4", "goldenrod", "goldenrod1", - "goldenrod2", "goldenrod3", "goldenrod4", "gray", "gray0", "gray1", "gray10", - "gray100", "gray11", "gray12", "gray13", "gray14", "gray15", "gray16", - "gray17", "gray18", "gray19", "gray2", "gray20", "gray21", "gray22", - "gray23", "gray24", "gray25", "gray26", "gray27", "gray28", "gray29", - "gray3", "gray30", "gray31", "gray32", "gray33", "gray34", "gray35", - "gray36", "gray37", "gray38", "gray39", "gray4", "gray40", "gray41", - "gray42", "gray43", "gray44", "gray45", "gray46", "gray47", "gray48", - "gray49", "gray5", "gray50", "gray51", "gray52", "gray53", "gray54", - "gray55", "gray56", "gray57", "gray58", "gray59", "gray6", "gray60", - "gray61", "gray62", "gray63", "gray64", "gray65", "gray66", "gray67", - "gray68", "gray69", "gray7", "gray70", "gray71", "gray72", "gray73", - "gray74", "gray75", "gray76", "gray77", "gray78", "gray79", "gray8", - "gray80", "gray81", "gray82", "gray83", "gray84", "gray85", "gray86", - "gray87", "gray88", "gray89", "gray9", "gray90", "gray91", "gray92", - "gray93", "gray94", "gray95", "gray96", "gray97", "gray98", "gray99", - "green", "green1", "green2", "green3", "green4", "greenyellow", "grey", - "grey0", "grey1", "grey10", "grey100", "grey11", "grey12", "grey13", - "grey14", "grey15", "grey16", "grey17", "grey18", "grey19", "grey2", - "grey20", "grey21", "grey22", "grey23", "grey24", "grey25", "grey26", - "grey27", "grey28", "grey29", "grey3", "grey30", "grey31", "grey32", - "grey33", "grey34", "grey35", "grey36", "grey37", "grey38", "grey39", - "grey4", "grey40", "grey41", "grey42", "grey43", "grey44", "grey45", - "grey46", "grey47", "grey48", "grey49", "grey5", "grey50", "grey51", - "grey52", "grey53", "grey54", "grey55", "grey56", "grey57", "grey58", - "grey59", "grey6", "grey60", "grey61", "grey62", "grey63", "grey64", - "grey65", "grey66", "grey67", "grey68", "grey69", "grey7", "grey70", - "grey71", "grey72", "grey73", "grey74", "grey75", "grey76", "grey77", - "grey78", "grey79", "grey8", "grey80", "grey81", "grey82", "grey83", - "grey84", "grey85", "grey86", "grey87", "grey88", "grey89", "grey9", - "grey90", "grey91", "grey92", "grey93", "grey94", "grey95", "grey96", - "grey97", "grey98", "grey99", "honeydew", "honeydew1", "honeydew2", - "honeydew3", "honeydew4", "hotpink", "hotpink1", "hotpink2", "hotpink3", - "hotpink4", "indianred", "indianred1", "indianred2", "indianred3", - "indianred4", "indigo", "invis", "ivory", "ivory1", "ivory2", "ivory3", - "ivory4", "khaki", "khaki1", "khaki2", "khaki3", "khaki4", "lavender", - "lavenderblush", "lavenderblush1", "lavenderblush2", "lavenderblush3", - "lavenderblush4", "lawngreen", "lemonchiffon", "lemonchiffon1", - "lemonchiffon2", "lemonchiffon3", "lemonchiffon4", "lightblue", "lightblue1", - "lightblue2", "lightblue3", "lightblue4", "lightcoral", "lightcyan", - "lightcyan1", "lightcyan2", "lightcyan3", "lightcyan4", "lightgoldenrod", - "lightgoldenrod1", "lightgoldenrod2", "lightgoldenrod3", "lightgoldenrod4", - "lightgoldenrodyellow", "lightgray", "lightgrey", "lightpink", "lightpink1", - "lightpink2", "lightpink3", "lightpink4", "lightsalmon", "lightsalmon1", - "lightsalmon2", "lightsalmon3", "lightsalmon4", "lightseagreen", - "lightskyblue", "lightskyblue1", "lightskyblue2", "lightskyblue3", - "lightskyblue4", "lightslateblue", "lightslategray", "lightslategrey", - "lightsteelblue", "lightsteelblue1", "lightsteelblue2", "lightsteelblue3", - "lightsteelblue4", "lightyellow", "lightyellow1", "lightyellow2", - "lightyellow3", "lightyellow4", "limegreen", "linen", "magenta", "magenta1", - "magenta2", "magenta3", "magenta4", "maroon", "maroon1", "maroon2", - "maroon3", "maroon4", "mediumaquamarine", "mediumblue", "mediumorchid", - "mediumorchid1", "mediumorchid2", "mediumorchid3", "mediumorchid4", - "mediumpurple", "mediumpurple1", "mediumpurple2", "mediumpurple3", - "mediumpurple4", "mediumseagreen", "mediumslateblue", "mediumspringgreen", - "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", - "mistyrose", "mistyrose1", "mistyrose2", "mistyrose3", "mistyrose4", - "moccasin", "navajowhite", "navajowhite1", "navajowhite2", "navajowhite3", - "navajowhite4", "navy", "navyblue", "none", "oldlace", "olivedrab", - "olivedrab1", "olivedrab2", "olivedrab3", "olivedrab4", "orange", "orange1", - "orange2", "orange3", "orange4", "orangered", "orangered1", "orangered2", - "orangered3", "orangered4", "orchid", "orchid1", "orchid2", "orchid3", - "orchid4", "palegoldenrod", "palegreen", "palegreen1", "palegreen2", - "palegreen3", "palegreen4", "paleturquoise", "paleturquoise1", - "paleturquoise2", "paleturquoise3", "paleturquoise4", "palevioletred", - "palevioletred1", "palevioletred2", "palevioletred3", "palevioletred4", - "papayawhip", "peachpuff", "peachpuff1", "peachpuff2", "peachpuff3", - "peachpuff4", "peru", "pink", "pink1", "pink2", "pink3", "pink4", "plum", - "plum1", "plum2", "plum3", "plum4", "powderblue", "purple", "purple1", - "purple2", "purple3", "purple4", "red", "red1", "red2", "red3", "red4", - "rosybrown", "rosybrown1", "rosybrown2", "rosybrown3", "rosybrown4", - "royalblue", "royalblue1", "royalblue2", "royalblue3", "royalblue4", - "saddlebrown", "salmon", "salmon1", "salmon2", "salmon3", "salmon4", - "sandybrown", "seagreen", "seagreen1", "seagreen2", "seagreen3", "seagreen4", - "seashell", "seashell1", "seashell2", "seashell3", "seashell4", "sienna", - "sienna1", "sienna2", "sienna3", "sienna4", "skyblue", "skyblue1", - "skyblue2", "skyblue3", "skyblue4", "slateblue", "slateblue1", "slateblue2", - "slateblue3", "slateblue4", "slategray", "slategray1", "slategray2", - "slategray3", "slategray4", "slategrey", "snow", "snow1", "snow2", "snow3", - "snow4", "springgreen", "springgreen1", "springgreen2", "springgreen3", - "springgreen4", "steelblue", "steelblue1", "steelblue2", "steelblue3", - "steelblue4", "tan", "tan1", "tan2", "tan3", "tan4", "thistle", "thistle1", - "thistle2", "thistle3", "thistle4", "tomato", "tomato1", "tomato2", - "tomato3", "tomato4", "transparent", "turquoise", "turquoise1", "turquoise2", - "turquoise3", "turquoise4", "violet", "violetred", "violetred1", - "violetred2", "violetred3", "violetred4", "wheat", "wheat1", "wheat2", - "wheat3", "wheat4", "white", "whitesmoke", "yellow", "yellow1", "yellow2", - "yellow3", "yellow4", "yellowgreen"]); - -# code from the GAP standard library -InstallMethod(GV_Pluralize, -"for an integer and a string", -[IsInt, IsString], -function(args...) - local nargs, i, count, include_num, str, len, out; - - nargs := Length(args); - if nargs >= 1 and IsInt(args[1]) and args[1] >= 0 then - i := 2; - count := args[1]; - include_num := true; - else - i := 1; - include_num := false; # if not given, assume pluralization is wanted. - fi; - - if not (nargs in [i, i + 1] and - IsString(args[i]) and - (nargs = i or IsString(args[i + 1]))) then - ErrorNoReturn("Usage: GV_Pluralize([, ][, ])"); - fi; - - str := args[i]; - len := Length(str); - - if len = 0 then - ErrorNoReturn("the argument must be a non-empty string"); - elif include_num and count = 1 then # no pluralization needed - return Concatenation("\>1\< ", str); - elif nargs = i + 1 then # pluralization given - out := args[i + 1]; - elif len <= 2 then - out := Concatenation(str, "s"); - - # Guess and return the plural form of . - # Inspired by the "Ruby on Rails" inflection rules. - - # Uncountable nouns - elif str in ["equipment", "information"] then - out := str; - - # Irregular plurals - elif str = "axis" then - out := "axes"; - elif str = "child" then - out := "children"; - elif str = "person" then - out := "people"; - - # Peculiar endings - elif EndsWith(str, "ix") or EndsWith(str, "ex") then - out := Concatenation(str{[1 .. len - 2]}, "ices"); - elif EndsWith(str, "x") then - out := Concatenation(str, "es"); - elif EndsWith(str, "tum") or EndsWith(str, "ium") then - out := Concatenation(str{[1 .. len - 2]}, "a"); - elif EndsWith(str, "sis") then - out := Concatenation(str{[1 .. len - 3]}, "ses"); - elif EndsWith(str, "fe") and not EndsWith(str, "ffe") then - out := Concatenation(str{[1 .. len - 2]}, "ves"); - elif EndsWith(str, "lf") or EndsWith(str, "rf") or EndsWith(str, "loaf") then - out := Concatenation(str{[1 .. len - 1]}, "ves"); - elif EndsWith(str, "y") and not str[len - 1] in "aeiouy" then - out := Concatenation(str{[1 .. len - 1]}, "ies"); - elif str{[len - 1, len]} in ["ch", "ss", "sh"] then - out := Concatenation(str, "es"); - elif EndsWith(str, "s") then - out := str; - - # Default to appending 's' - else - out := Concatenation(str, "s"); - fi; - - if include_num then - return Concatenation("\>", String(args[1]), "\< ", out); - fi; - return out; -end); - -############################################################################### -# Family + type -############################################################################### - -BindGlobal("GV_ObjectFamily", - NewFamily("GV_ObjectFamily", - IsGVObject)); - -BindGlobal("GV_MapType", NewType(GV_ObjectFamily, - IsGV_Map and - IsComponentObjectRep and - IsAttributeStoringRep)); - -BindGlobal("GV_DigraphType", NewType(GV_ObjectFamily, - IsGVDigraph and - IsComponentObjectRep and - IsAttributeStoringRep)); - -BindGlobal("GV_GraphType", NewType(GV_ObjectFamily, - IsGVGraph and - IsComponentObjectRep and - IsAttributeStoringRep)); - -BindGlobal("GV_NodeType", NewType(GV_ObjectFamily, - IsGVNode and - IsComponentObjectRep and - IsAttributeStoringRep)); - -BindGlobal("GV_EdgeType", NewType(GV_ObjectFamily, - IsGVEdge and - IsComponentObjectRep and - IsAttributeStoringRep)); - -BindGlobal("GV_ContextType", NewType(GV_ObjectFamily, - IsGVContext and - IsComponentObjectRep and - IsAttributeStoringRep)); - -InstallMethod(\=, "for IsGVNode and IsGVNode", -[IsGVNode, IsGVNode], -{n1, n2} -> GraphvizName(n1) = GraphvizName(n2)); - -############################################################################### -# Constructors etc -############################################################################### - -DeclareOperation("GV_Node", [IsGVGraph, IsString]); -DeclareOperation("GV_Edge", [IsGVGraph, IsGVNode, IsGVNode]); -DeclareOperation("GV_Graph", [IsGVGraph, IsString]); -DeclareOperation("GV_Digraph", [IsGVDigraph, IsString]); -DeclareOperation("GV_Context", [IsGVGraph, IsString]); -DeclareOperation("GV_Map", []); - -InstallMethod(GV_Map, "for no args", -[], {} -> Objectify(GV_MapType, rec(Data := rec()))); - -InstallMethod(GV_Node, "for a string", -[IsGVGraph, IsString], -function(graph, name) - local out; - if Length(name) = 0 then - ErrorNoReturn("the 2nd argument (string/node name) cannot be empty"); - fi; - out := Objectify(GV_NodeType, - rec( - Name := name, - Attrs := GV_Map(), - Idx := GV_GetCounter(graph))); - GV_IncCounter(graph); - return out; -end); - -InstallMethod(GV_Edge, "for two graphviz nodes", -[IsGVGraph, IsGVNode, IsGVNode], -function(graph, head, tail) - local out; - out := Objectify(GV_EdgeType, - rec( - Name := "", - Head := head, - Tail := tail, - Attrs := GV_Map(), - Idx := GV_GetCounter(graph))); - GV_IncCounter(graph); - return out; -end); - -# Graph constructors - -InstallMethod(GV_Digraph, -"for a graphviz digraph and a string", -[IsGVDigraph, IsString], -function(parent, name) - local out; - - out := GraphvizDigraph(name); - out!.Parent := parent; - out!.Idx := GV_GetCounter(parent); - - GV_IncCounter(parent); - return out; -end); - -InstallMethod(GV_Graph, -"for a graphviz graph and a string", -[IsGVGraph, IsString], -function(parent, name) - local out; - - out := GraphvizGraph(name); - out!.Parent := parent; - out!.Idx := GV_GetCounter(parent); - - GV_IncCounter(parent); - return out; -end); - -InstallMethod(GV_Context, -"for a string and a positive integer", -[IsGVGraph, IsString], -function(parent, name) - local out; - - out := Objectify(GV_ContextType, - rec( - Name := name, - Subgraphs := GV_Map(), - Nodes := GV_Map(), - Edges := [], - Attrs := [], - Parent := parent, - Idx := GV_GetCounter(parent), - Counter := 1)); - - GV_IncCounter(parent); - return out; -end); - -# public constructors - -InstallMethod(GraphvizGraph, -"for a string", -[IsString], +InstallMethod(GraphvizGraph, "for a string", [IsString], function(name) return Objectify(GV_GraphType, rec( @@ -454,14 +26,9 @@ function(name) Counter := 1)); end); -InstallMethod(GraphvizGraph, -"for an object", -[IsObject], -o -> GraphvizGraph(ViewString(o))); +InstallMethod(GraphvizGraph, "for no args", [], {} -> GraphvizGraph("")); -InstallMethod(GraphvizDigraph, -"for a string", -[IsString], +InstallMethod(GraphvizDigraph, "for a string", [IsString], function(name) return Objectify(GV_DigraphType, rec( @@ -475,76 +42,28 @@ function(name) Counter := 1)); end); -InstallMethod(GraphvizDigraph, -"for an object", -[IsObject], -o -> GraphvizDigraph(ViewString(o))); - -InstallMethod(GraphvizGraph, "for no args", [], {} -> GraphvizGraph("")); InstallMethod(GraphvizDigraph, "for no args", [], {} -> GraphvizDigraph("")); -# ########################################################### -# Graphviz Map Functions -# ########################################################### - -InstallOtherMethod(\[\], -"for a graphviz map and an object", -[IsGV_Map, IsObject], -function(m, o) - if IsBound(m[o]) then - return m!.Data.(o); - fi; - return fail; -end); - -InstallOtherMethod(\[\]\:\=, -"for a graphviz map and two objects", -[IsGV_Map, IsObject, IsObject], -function(m, key, val) - m!.Data.(key) := val; -end); - -InstallOtherMethod(Unbind\[\], -"for a graphviz map and an object", -[IsGV_Map, IsObject], -function(m, key) - Unbind(m!.Data.(key)); -end); - -InstallOtherMethod(IsBound\[\], -"for a graphviz map and an object", -[IsGV_Map, IsObject], -{m, key} -> IsBound(m!.Data.(key))); - -DeclareOperation("GV_MapNames", [IsGV_Map]); - -InstallMethod(GV_MapNames, "for a graphviz map", -[IsGV_Map], m -> RecNames(m!.Data)); - -InstallMethod(ViewString, "for a graphviz map", [IsGV_Map], -m -> String(m!.Data)); - -InstallMethod(Size, "for a graphviz map", -[IsGV_Map], m -> Length(GV_MapNames(m))); - -############################################################ -# Stringify -############################################################ +############################################################################# +# ViewString +############################################################################# -InstallMethod(ViewString, "for a graphviz node", [IsGVNode], -n -> StringFormatted("", GraphvizName(n))); +InstallMethod(PrintString, "for a graphviz node", [IsGraphvizNode], +n -> StringFormatted("", GraphvizName(n))); -InstallMethod(ViewString, "for a graphviz edge", [IsGVEdge], +InstallMethod(PrintString, "for a graphviz edge", [IsGraphvizEdge], function(e) local head, tail; head := GraphvizHead(e); tail := GraphvizTail(e); + return StringFormatted("", GraphvizName(head), GraphvizName(tail)); end); -InstallMethod(ViewString, "for a graphviz graph", [IsGVGraph], +InstallMethod(PrintString, "for a graphviz (di)graph or context", +[IsGraphvizGraphDigraphOrContext], function(g) local result, edges, nodes, kind; @@ -553,9 +72,9 @@ function(g) nodes := Length(GV_MapNames(GraphvizNodes(g))); - if IsGVDigraph(g) then + if IsGraphvizDigraph(g) then kind := "digraph"; - elif IsGVContext(g) then + elif IsGraphvizContext(g) then kind := "context"; else kind := "graph"; @@ -564,263 +83,158 @@ function(g) Append(result, StringFormatted(" "" then - Append(result, StringFormatted("{} ", GraphvizName(g))); + Append(result, StringFormatted("\"{}\" ", GraphvizName(g))); fi; Append(result, StringFormatted("with {} ", GV_Pluralize(nodes, "node"))); Append(result, StringFormatted("and {}>", GV_Pluralize(edges, "edge"))); + # TODO add more info like that about number of subgraphs + contexts return result; end); -############################################################ +############################################################################# # Getters -############################################################ +############################################################################# -InstallMethod(GraphvizName, "for a graphviz object", [IsGVObject], x -> x!.Name); +InstallMethod(GraphvizName, "for a graphviz object", [IsGraphvizObject], +x -> x!.Name); -InstallMethod(GraphvizAttrs, "for a graphviz object", [IsGVObject], +InstallMethod(GraphvizAttrs, "for a graphviz object", [IsGraphvizObject], x -> x!.Attrs); -# TODO remove, the returned value is mutable, looks like a record but isn't -# one. Mutability is a problem, since if we do GraphvizNodes(gv)[1] := 2; then -# the object is corrupt. -InstallMethod(GraphvizNodes, "for a graphviz graph", [IsGVGraph], -x -> x!.Nodes); - -InstallMethod(GraphvizNode, "for a graphviz graph and object", -[IsGVGraph, IsObject], {gv, obj} -> gv!.Nodes[String(obj)]); +InstallMethod(GraphvizNodes, "for a graphviz (di)graph or context", +[IsGraphvizGraphDigraphOrContext], x -> x!.Nodes); -# TODO remove for the same reason as GraphvizNodes, unless we can make the list -# immutable or return a copy. -InstallMethod(GraphvizEdges, "for a graphviz graph", -[IsGVGraph], x -> x!.Edges); +InstallMethod(GraphvizEdges, "for a graphviz (di)graph or context", +[IsGraphvizGraphDigraphOrContext], x -> x!.Edges); InstallMethod(GraphvizEdges, -"for a graphviz graph, object, and object", -[IsGVGraph, IsObject, IsObject], +"for a graphviz (di)graph or context, object, and object", +[IsGraphvizGraphDigraphOrContext, IsObject, IsObject], function(gv, head, tail) - head := GraphvizNode(gv, head); - tail := GraphvizNode(gv, tail); - # TODO if head = fail then... - return Filtered(GraphvizEdges(gv), x -> GraphvizHead(x) = head and - GraphvizTail(x) = tail); -end); - -InstallMethod(GraphvizSubgraphs, "for a graphviz graph", [IsGVGraph], -x -> x!.Subgraphs); - -InstallMethod(GraphvizTail, "for a graphviz edge", [IsGVEdge], x -> x!.Tail); - -InstallMethod(GraphvizHead, "for a graphviz edge", [IsGVEdge], x -> x!.Head); + local nhead, ntail; -InstallMethod(GraphvizGetSubgraph, -"for a graphviz graph and string", -[IsGVGraph, IsString], -{x, name} -> GraphvizSubgraphs(x)[name]); - -InstallMethod(GraphvizGetSubgraph, -"for a graphviz graph and an object", -[IsGVGraph, IsObject], -{x, o} -> GraphvizSubgraphs(x)[ViewString(o)]); - -InstallMethod(GV_IncCounter, -"for a graphviz graph", -[IsGVGraph], -function(x) - x!.Counter := x!.Counter + 1; + nhead := GraphvizNodes(gv)[head]; + if nhead = fail then + ErrorFormatted("the 2nd argument \"{}\" (head of an edge) is not a ", + "node of the 1st argument (a graphviz graph or digraph)", + head); + fi; + ntail := GraphvizNodes(gv)[tail]; + if ntail = fail then + ErrorFormatted("the 3rd argument \"{}\" (tail of an edge) is not a ", + "node of the 1st argument (a graphviz graph or digraph)", + tail); + fi; + return Filtered(GraphvizEdges(gv), + x -> GraphvizHead(x) = nhead and GraphvizTail(x) = ntail); end); -InstallMethod(GV_GetCounter, "for a graphviz graph", [IsGVGraph], -x -> x!.Counter); +InstallMethod(GraphvizSubgraphs, "for a graphviz (di)graph or context", +[IsGraphvizGraphDigraphOrContext], x -> x!.Subgraphs); + +InstallMethod(GraphvizTail, "for a graphviz edge", [IsGraphvizEdge], +x -> x!.Tail); -# Converting strings +InstallMethod(GraphvizHead, "for a graphviz edge", [IsGraphvizEdge], +x -> x!.Head); -DeclareOperation("GV_EnsureString", [IsObject]); -# TODO required? Replace with AsString -InstallMethod(GV_EnsureString, -"for an object", -[IsObject], ViewString); +# Operators for nodes -InstallMethod(GV_EnsureString, -"for a string", -[IsString], -x -> x); +InstallMethod(\=, "for graphviz nodes", +[IsGraphvizNode, IsGraphvizNode], IsIdenticalObj); # Accessing node attributes -InstallOtherMethod(\[\], -"for a graphviz node and a string", -[IsGVNode, IsString], +InstallMethod(\[\], "for a graphviz node and a string", +[IsGraphvizNode, IsString], {node, key} -> GraphvizAttrs(node)[key]); -InstallOtherMethod(\[\], -"for a graphviz node and an object", -[IsGVNode, IsObject], -{node, key} -> node[GV_EnsureString(key)]); +InstallMethod(\[\], "for a graphviz node and an object", +[IsGraphvizNode, IsObject], +{node, key} -> node[String(key)]); # Setting node attributes -InstallOtherMethod(\[\]\:\=, -"for a graphviz node and two strings", -[IsGVNode, IsString, IsString], +InstallMethod(\[\]\:\=, "for a graphviz node and two strings", +[IsGraphvizNode, IsString, IsString], function(node, key, val) GraphvizAttrs(node)[key] := val; end); -InstallOtherMethod(\[\]\:\=, -"for a graphviz node and two strings", -[IsGVNode, IsObject, IsObject], +InstallMethod(\[\]\:\=, "for a graphviz node and two strings", +[IsGraphvizNode, IsObject, IsObject], function(node, key, val) - node[GV_EnsureString(key)] := GV_EnsureString(val); + node[String(key)] := String(val); end); # Accessing edge attributes -InstallOtherMethod(\[\], -"for a graphviz node and a string", -[IsGVEdge, IsString], +InstallMethod(\[\], "for a graphviz edge and a string", +[IsGraphvizEdge, IsString], {edge, key} -> GraphvizAttrs(edge)[key]); -InstallOtherMethod(\[\], -"for a graphviz node and an object", -[IsGVEdge, IsObject], -{edge, key} -> edge[GV_EnsureString(key)]); +InstallMethod(\[\], "for a graphviz edge and an object", +[IsGraphvizEdge, IsObject], +{edge, key} -> edge[String(key)]); -InstallOtherMethod(\[\]\:\=, -"for a graphviz node and a string", -[IsGVEdge, IsString, IsString], +InstallMethod(\[\]\:\=, "for a graphviz edge and a string", +[IsGraphvizEdge, IsString, IsString], function(edge, key, val) GraphvizAttrs(edge)[key] := val; end); -InstallOtherMethod(\[\]\:\=, -"for a graphviz node and an object", -[IsGVEdge, IsObject, IsObject], +InstallMethod(\[\]\:\=, "for a graphviz edge and an object", +[IsGraphvizEdge, IsObject, IsObject], function(edge, key, val) - edge[GV_EnsureString(key)] := GV_EnsureString(val); + edge[String(key)] := String(val); end); -InstallOtherMethod(\[\], -"for a graphviz graph and string", -[IsGVGraph, IsString], -{graph, node} -> GraphvizNodes(graph)[node]); - -InstallOtherMethod(\[\], -"for a graphviz graph and string", -[IsGVGraph, IsObject], -{g, o} -> g[ViewString(o)]); - -DeclareOperation("GV_HasNode", [IsGVGraph, IsObject]); - -InstallMethod(GV_HasNode, -"for a graphviz graph", -[IsGVGraph, IsString], -{g, name} -> name in GV_MapNames(GraphvizNodes(g))); - -DeclareOperation("GV_GetParent", [IsGVGraph]); +InstallMethod(\=, "for graphviz edges", +[IsGraphvizEdge, IsGraphvizEdge], IsIdenticalObj); -InstallMethod(GV_GetParent, -"for a graphviz graph", -[IsGVGraph], graph -> graph!.Parent); +# Accessor for graphs and digraphs -DeclareOperation("GV_GraphTreeSearch", [IsGVGraph, IsFunction]); - -InstallMethod(GV_GraphTreeSearch, -"for a graphviz graph and a predicate", -[IsGVGraph, IsFunction], -function(graph, pred) - local seen, to_visit, g, key, subgraph, parent; - seen := [graph]; - to_visit := [graph]; - - while Length(to_visit) > 0 do - g := Remove(to_visit, Length(to_visit)); - - # Check this graph - if pred(g) then - return g; - fi; - - # add subgraphs to list of to visit if not visited - for key in GV_MapNames(GraphvizSubgraphs(g)) do - subgraph := GraphvizSubgraphs(g)[key]; - if not ForAny(seen, s -> IsIdenticalObj(s, subgraph)) then - Add(seen, subgraph); - Add(to_visit, subgraph); - fi; - od; - - # add parent if not visited - parent := GV_GetParent(g); - if not IsGVGraph(parent) then - continue; - fi; - if not ForAny(seen, s -> IsIdenticalObj(s, parent)) then - Add(seen, parent); - Add(to_visit, parent); - fi; - od; +InstallMethod(\[\], "for a graphviz (di)graph or context and string", +[IsGraphvizGraphDigraphOrContext, IsString], +{graph, node} -> GraphvizNodes(graph)[node]); - return fail; -end); +InstallMethod(\[\], "for a graphviz (di)graph or context and object", +[IsGraphvizGraphDigraphOrContext, IsObject], +{g, o} -> g[String(o)]); -DeclareOperation("GV_FindGraphWithNode", [IsGVGraph, IsString]); -InstallMethod(GV_FindGraphWithNode, -"for a graphviz graph and a node", -[IsGVGraph, IsString], -{g, n} -> GV_GraphTreeSearch(g, v -> v[n] <> fail)); - -DeclareOperation("GV_GetRoot", [IsGVGraph]); -InstallMethod(GV_GetRoot, -"for a graphviz graph", -[IsGVGraph], -function(graph) - while GV_GetParent(graph) <> fail do - graph := GV_GetParent(graph); - od; - return graph; -end); - -InstallMethod(GraphvizFindGraph, -"for a graphviz graph and a string", -[IsGVGraph, IsString], +InstallMethod(GraphvizFindSubgraphRecursive, +"for a graphviz (di)graph or context and a string", +[IsGraphvizGraphDigraphOrContext, IsString], {g, s} -> GV_GraphTreeSearch(g, v -> GraphvizName(v) = s)); -InstallMethod(GraphvizFindGraph, -"for a graphviz graph and a string", -[IsGVGraph, IsObject], -{g, o} -> GraphvizFindGraph(g, ViewString(o))); - -InstallMethod(GV_FindNode, -"for a graphviz graph and a string", -[IsGVGraph, IsString], -function(g, n) - local graph; - graph := GV_FindGraphWithNode(g, n); - if graph = fail then - return graph; - fi; - return graph[n]; -end); +InstallMethod(GraphvizFindSubgraphRecursive, +"for a graphviz (di)graph or context and a string", +[IsGraphvizGraphDigraphOrContext, IsObject], +{g, o} -> GraphvizFindSubgraphRecursive(g, String(o))); -############################################################ -# Setters -############################################################ +############################################################################# +# GraphvizSetName +############################################################################# -InstallMethod(GraphvizSetName, "for a graphviz object and string", -[IsGVGraph, IsString], +InstallMethod(GraphvizSetName, "for a graphviz (di)graph or context and string", +[IsGraphvizGraphDigraphOrContext, IsString], function(x, name) x!.Name := name; return x; end); -InstallMethod(GraphvizSetName, "for a graphviz object and string", -[IsGVGraph, IsObject], -{g, o} -> GraphvizSetName(g, ViewString(o))); +InstallMethod(GraphvizSetName, "for a graphviz (di)graph or context and string", +[IsGraphvizGraphDigraphOrContext, IsObject], +{g, o} -> GraphvizSetName(g, String(o))); + +############################################################################# +# GraphvizSetAttr(s) +############################################################################# InstallMethod(GraphvizSetAttrs, "for a graphviz object and record", -[IsGVObject, IsRecord], +[IsGraphvizObject, IsRecord], function(x, attrs) local name; for name in RecNames(attrs) do @@ -829,42 +243,38 @@ function(x, attrs) return x; end); -InstallMethod(GraphvizSetAttr, "for a graphviz object, object and object", -[IsGVObject, IsObject, IsObject], +InstallMethod(GraphvizSetAttr, "for a graphviz node or edge, object, and object", +[IsGraphvizNodeOrEdge, IsObject, IsObject], function(x, name, value) - local msg; if not name in GV_KNOWN_ATTRS then - msg := Concatenation( - StringFormatted("unknown attribute \"{}\", the", name), - " graphviz object may no longer be valid, it can", - " be removed using GraphvizRemoveAttr"); - Info(InfoWarning, 1, msg); + Info(InfoWarning, 1, + StringFormatted("unknown attribute \"{}\", the", name), + " graphviz object may no longer be valid, it can", + " be removed using GraphvizRemoveAttr"); fi; GraphvizAttrs(x)[String(name)] := String(value); return x; end); -InstallMethod(GraphvizSetAttr, "for a graphviz graph, object and object", -[IsGVGraph, IsObject, IsObject], +InstallMethod(GraphvizSetAttr, +"for a graphviz object with subobjects, object, and object", +[IsGraphvizGraphDigraphOrContext, IsObject, IsObject], function(x, name, value) - local attrs, string, msg; + local attrs, string; if not name in GV_KNOWN_ATTRS then - msg := Concatenation( - StringFormatted("unknown attribute \"{}\", the", name), - " graphviz object may no longer be valid, it can", - " be removed using GraphvizRemoveAttr"); - Info(InfoWarning, 1, msg); + Info(InfoWarning, 1, + StringFormatted("unknown attribute \"{}\", the", name), + " graphviz object may no longer be valid, it can", + " be removed using GraphvizRemoveAttr"); fi; attrs := GraphvizAttrs(x); name := String(name); value := String(value); - if ' ' in name then - name := StringFormatted("\"{}\"", name); - fi; if ' ' in value then + # Replace with call to GV_QuoteName or whatever TODO value := StringFormatted("\"{}\"", value); fi; @@ -873,8 +283,8 @@ function(x, name, value) return x; end); -InstallMethod(GraphvizSetAttr, "for a graphviz object, object and object", -[IsGVGraph, IsObject], +InstallMethod(GraphvizSetAttr, "for a graphviz (di)graph or context and object", +[IsGraphvizGraphDigraphOrContext, IsObject], function(x, value) local attrs; attrs := GraphvizAttrs(x); @@ -883,38 +293,12 @@ function(x, value) return x; end); -InstallMethod(GraphvizSetLabel, -"for a graphviz object and an object", -[IsGVObject, IsObject], -{x, label} -> GraphvizSetAttr(x, "label", label)); - -InstallMethod(GraphvizSetColor, -"for a graphviz object and an object", -[IsGVObject, IsObject], -{x, color} -> GraphvizSetAttr(x, "color", color)); - -DeclareOperation("GV_AddNode", [IsGVGraph, IsGVNode]); -InstallMethod(GV_AddNode, -"for a graphviz graph and node", -[IsGVGraph, IsGVNode], -function(x, node) - local found, error, name, nodes; - name := GraphvizName(node); - nodes := GraphvizNodes(x); - - # dont add if already node with the same name - found := GV_FindGraphWithNode(x, name); - if found <> fail then - error := "Already node with name {} in graph {}."; - ErrorNoReturn(StringFormatted(error, name, GraphvizName(found))); - fi; - - nodes[name] := node; - return x; -end); +############################################################################# +# GraphvizAddNode +############################################################################# -InstallMethod(GraphvizAddNode, "for a graphviz graph and string", -[IsGVGraph, IsString], +InstallMethod(GraphvizAddNode, "for a graphviz (di)graph or context and string", +[IsGraphvizGraphDigraphOrContext, IsString], function(x, name) local node; node := GV_Node(x, name); @@ -922,65 +306,26 @@ function(x, name) return node; end); -# TODO required? -InstallMethod(GraphvizAddNode, "for a graphviz graph and string", -[IsGVGraph, IsGVNode], -function(_, __) # gaplint: disable=analyse-lvars - local error; - error := "Cannot add node objects directly to graphs. "; - error := Concatenation(error, "Please use the node's name."); - ErrorNoReturn(error); +InstallMethod(GraphvizAddNode, "for a graphviz (di)graph or context and string", +[IsGraphvizGraphDigraphOrContext, IsGraphvizNode], +function(gv, name) # gaplint: disable=analyse-lvars + ErrorNoReturn("it is not currently possible to add Graphviz node ", + "objects directly to Graphviz graphs or digraphs, use ", + "the node's name instead"); end); InstallMethod(GraphvizAddNode, -"for a graphviz graph and string", -[IsGVGraph, IsObject], -{x, name} -> GraphvizAddNode(x, ViewString(name))); - -DeclareOperation("GV_AddEdge", [IsGVGraph, IsGVEdge]); -InstallMethod(GV_AddEdge, -"for a graphviz graph and edge", -[IsGVGraph, IsGVEdge], -function(x, edge) - local head, head_name, tail_name, tail, hg, error, tg; - - head := GraphvizHead(edge); - tail := GraphvizTail(edge); - head_name := GraphvizName(head); - tail_name := GraphvizName(tail); - hg := GV_FindGraphWithNode(x, head_name); - tg := GV_FindGraphWithNode(x, tail_name); - - # if not already existing, add the nodes to the graph - if hg = fail then - GV_AddNode(x, head); - fi; - if tg = fail then - GV_AddNode(x, tail); - fi; +"for a graphviz (di)graph or context and string", +[IsGraphvizGraphDigraphOrContext, IsObject], +{x, name} -> GraphvizAddNode(x, String(name))); - # make sure the nodes exist / are the same as existing ones - if hg <> fail and not IsIdenticalObj(head, hg[head_name]) then - # TODO improve - ErrorFormatted("Different node in graph {} with name {}", - GraphvizName(hg), - head_name); - fi; - if tg <> fail and not IsIdenticalObj(tail, tg[tail_name]) then - # TODO improve - error := "Different node in graph {} with name {}."; - ErrorNoReturn(StringFormatted(error, - GraphvizName(tg), - tail_name)); - fi; - - Add(x!.Edges, edge); - return x; -end); +############################################################################# +# GraphvizAddEdge +############################################################################# InstallMethod(GraphvizAddEdge, -"for a graphviz graph and two graphviz nodes", -[IsGVGraph, IsGVNode, IsGVNode], +"for a graphviz (di)graph or context and two graphviz nodes", +[IsGraphvizGraphDigraphOrContext, IsGraphvizNode, IsGraphvizNode], function(x, head, tail) local edge, head_name, tail_name; @@ -1001,8 +346,8 @@ function(x, head, tail) end); InstallMethod(GraphvizAddEdge, -"for a graphviz graph and two strings", -[IsGVGraph, IsString, IsString], +"for a graphviz (di)graph or context and two strings", +[IsGraphvizGraphDigraphOrContext, IsString, IsString], function(x, head, tail) local head_node, tail_node; @@ -1020,36 +365,36 @@ function(x, head, tail) end); InstallMethod(GraphvizAddEdge, -"for a graphviz graph and two objects", -[IsGVGraph, IsObject, IsObject], -function(x, o1, o2) - if not IsString(o1) then - o1 := ViewString(o1); - fi; - if not IsString(o2) then - o2 := ViewString(o2); - fi; - return GraphvizAddEdge(x, o1, o2); -end); +"for a graphviz (di)graph or context and two objects", +[IsGraphvizGraphDigraphOrContext, IsObject, IsObject], +{gv, o1, o2} -> GraphvizAddEdge(gv, String(o1), String(o2))); + +############################################################################# +# GraphvizAddSubgraph +############################################################################# InstallMethod(GraphvizAddSubgraph, -"for a graphviz graph and string", -[IsGVGraph, IsString], -function(graph, name) - local error, subgraphs, subgraph; +"for a graphviz (di)graph or context and string", +[IsGraphvizGraphDigraphOrContext, IsString], +function(gv, name) + local subgraphs, root, subgraph; - subgraphs := GraphvizSubgraphs(graph); + subgraphs := GraphvizSubgraphs(gv); if IsBound(subgraphs[name]) then - error := "The graph already contains a subgraph with name {}."; - ErrorNoReturn(StringFormatted(error, name)); + ErrorFormatted("the 1st argument (a graphviz (di)graph/context) ", + "already has a subgraph with name \"{}\"", name); fi; - if IsGVDigraph(graph) then - subgraph := GV_Digraph(graph, name); - elif IsGVGraph(graph) then - subgraph := GV_Graph(graph, name); + if IsGraphvizContext(gv) then + root := GV_EnclosingNonContext(gv); else - ErrorNoReturn("Filter must be a filter for a graph category."); + root := gv; + fi; + + if IsGraphvizDigraph(root) then + subgraph := GV_Digraph(root, name); + elif IsGraphvizGraph(root) then + subgraph := GV_Graph(root, name); fi; subgraphs[name] := subgraph; @@ -1057,28 +402,30 @@ function(graph, name) end); InstallMethod(GraphvizAddSubgraph, -"for a graphviz graph and an object", -[IsGVGraph, IsObject], -{g, o} -> GraphvizAddSubgraph(g, ViewString(o))); +"for a graphviz (di)graph or context and an object", +[IsGraphvizGraphDigraphOrContext, IsObject], +{g, o} -> GraphvizAddSubgraph(g, String(o))); -InstallMethod(GraphvizAddSubgraph, -"for a grpahviz graph", -[IsGVGraph], -function(graph) - return GraphvizAddSubgraph(graph, StringFormatted("no_name_{}", - String(GV_GetCounter(graph)))); -end); +InstallMethod(GraphvizAddSubgraph, "for a graphviz (di)graph or context", +[IsGraphvizGraphDigraphOrContext], +graph -> GraphvizAddSubgraph(graph, + StringFormatted("no_name_{}", + GV_GetCounter(graph)))); InstallMethod(GraphvizAddContext, -"for a graphviz graph and a string", -[IsGVGraph, IsString], +"for a graphviz (di)graph or context and a string", +[IsGraphvizGraphDigraphOrContext, IsString], function(graph, name) - local ctx, error, subgraphs; + local subgraphs, ctx; subgraphs := GraphvizSubgraphs(graph); + # TODO is GraphvizSubgraphs appropriately named? It seems to contain both + # contexts and subgraphs, rather than just subgraphs as the name suggests + # See https://github.com/digraphs/graphviz/issues/19 if IsBound(subgraphs[name]) then - error := "The graph already contains a subgraph with name {}."; - ErrorNoReturn(StringFormatted(error, name)); + ErrorFormatted("the 1st argument (a graphviz (di)graph/context) ", + "already has a context or subgraph with name \"{}\"", + name); fi; ctx := GV_Context(graph, name); @@ -1087,29 +434,34 @@ function(graph, name) end); InstallMethod(GraphvizAddContext, -"for a graphviz graph", -[IsGVGraph], -g -> GraphvizAddContext(g, StringFormatted("no_name_{}", - String(GV_GetCounter(g))))); +"for a graphviz (di)graph or context", +[IsGraphvizGraphDigraphOrContext], +g -> GraphvizAddContext(g, StringFormatted("no_name_{}", GV_GetCounter(g)))); InstallMethod(GraphvizAddContext, -"for a graphviz graph and an object", -[IsGVGraph, IsObject], -{g, o} -> GraphvizAddContext(g, ViewString(o))); +"for a graphviz (di)graph or context and an object", +[IsGraphvizGraphDigraphOrContext, IsObject], +{g, o} -> GraphvizAddContext(g, String(o))); -InstallMethod(GraphvizRemoveNode, "for a graphviz graph and node", -[IsGVGraph, IsGVNode], +InstallMethod(GraphvizRemoveNode, "for a graphviz (di)graph or context and node", +[IsGraphvizGraphDigraphOrContext, IsGraphvizNode], {g, node} -> GraphvizRemoveNode(g, GraphvizName(node))); -# TODO GraphvizRemoveEdges(gv, n1, n2) - -InstallMethod(GraphvizRemoveNode, "for a graphviz graph and a string", -[IsGVGraph, IsString], +InstallMethod(GraphvizRemoveNode, +"for a graphviz (di)graph or context and a string", +[IsGraphvizGraphDigraphOrContext, IsString], function(g, name) local nodes; - # TODO error if there's no such node nodes := GraphvizNodes(g); - Unbind(nodes[name]); + if nodes[name] <> fail then + Unbind(nodes[name]); + else + # Don't just silently do nothing + ErrorFormatted("the 2nd argument (node name string) \"{}\"", + " is not a node of the 1st argument (a graphviz", + " (di)graph/context)", + name); + fi; # remove incident edges GraphvizFilterEdges(g, @@ -1123,12 +475,14 @@ function(g, name) return g; end); -InstallMethod(GraphvizRemoveNode, "for a graphviz graph and a string", -[IsGVGraph, IsObject], -{g, o} -> GraphvizRemoveNode(g, ViewString(o))); +InstallMethod(GraphvizRemoveNode, +"for a graphviz (di)graph or context and a string", +[IsGraphvizGraphDigraphOrContext, IsObject], +{g, o} -> GraphvizRemoveNode(g, String(o))); -InstallMethod(GraphvizFilterEdges, "for a graphviz graph and edge filter", -[IsGVGraph, IsFunction], +InstallMethod(GraphvizFilterEdges, +"for a graphviz (di)graph or context and edge filter", +[IsGraphvizGraphDigraphOrContext, IsFunction], function(g, filter) local edge, idx, edges; @@ -1145,15 +499,16 @@ function(g, filter) return g; end); -InstallMethod(GraphvizFilterEnds, "for a graphviz graph and two strings", -[IsGVGraph, IsString, IsString], +InstallMethod(GraphvizRemoveEdges, +"for a graphviz (di)graph or context, string, and string", +[IsGraphvizGraphDigraphOrContext, IsString, IsString], function(g, hn, tn) GraphvizFilterEdges(g, function(e) local head, tail, tmp; head := GraphvizHead(e); tail := GraphvizTail(e); - if IsGVDigraph(g) then + if IsGraphvizDigraph(g) then return tn <> GraphvizName(tail) or hn <> GraphvizName(head); else tmp := tn <> GraphvizName(tail) or hn <> GraphvizName(head); @@ -1164,30 +519,26 @@ function(g, hn, tn) return g; end); -InstallMethod(GraphvizFilterEnds, "for a graphviz graph and two strings", -[IsGVGraph, IsObject, IsObject], -function(g, o1, o2) - if not IsString(o1) then - o1 := ViewString(o1); - fi; - if not IsString(o2) then - o2 := ViewString(o2); - fi; - - GraphvizFilterEnds(g, o1, o2); -end); +InstallMethod(GraphvizRemoveEdges, +"for a graphviz (di)graph or context, object, and object", +[IsGraphvizGraphDigraphOrContext, IsObject, IsObject], +{gv, o1, o2} -> GraphvizRemoveEdges(gv, String(o1), String(o2))); InstallMethod(GraphvizRemoveAttr, "for a graphviz object and an object", -[IsGVObject, IsObject], +[IsGraphvizObject, IsObject], function(obj, attr) local attrs; attrs := GraphvizAttrs(obj); + # TODO error if no such attr? Unbind(attrs[String(attr)]); return obj; end); -InstallMethod(GraphvizRemoveAttr, "for a graphviz graph and an object", -[IsGVGraph, IsObject], +# TODO this doesn't currently work as intended, see: +# https://github.com/digraphs/graphviz/issues/23 +InstallMethod(GraphvizRemoveAttr, +"for a graphviz (di)graph or context and an object", +[IsGraphvizGraphDigraphOrContext, IsObject], function(obj, attr) local attrs; attrs := GraphvizAttrs(obj); @@ -1195,302 +546,36 @@ function(obj, attr) return obj; end); -# ############################################################################## -# Stringifying -# ############################################################################## - -# @ Return DOT graph head line. -InstallMethod(GV_StringifyGraphHead, "for a string", [IsGVGraph], -graph -> StringFormatted("graph {} {{\n", GraphvizName(graph))); - -# @ Return DOT digraph head line. -InstallMethod(GV_StringifyDigraphHead, "for a string", [IsGVDigraph], -graph -> StringFormatted("digraph {} {{\n", GraphvizName(graph))); - -# @ Return DOT subgraph head line. -InstallMethod(GV_StringifySubgraphHead, "for a string", [IsGVGraph], -graph -> StringFormatted("subgraph {} {{\n", GraphvizName(graph))); - -# @ Return DOT subgraph head line. -InstallMethod(GV_StringifyContextHead, "for a string", [IsGVContext], -graph -> StringFormatted("// {} context \n", GraphvizName(graph))); - -BindGlobal("GV_StringifyNodeName", -function(node) - local name, old; - - Assert(0, IsGVNode(node)); - name := GraphvizName(node); - if (ForAny("- .+", x -> x in name) - or (IsDigitChar(First(name)) and IsAlphaChar(Last(name)))) - and not StartsWith(name, "\"") then - old := name; - name := StringFormatted("\"{}\"", name); - Info(InfoWarning, - 1, - "invalid node name ", - old, - " using ", - name, - " instead"); - fi; - return name; -end); - -# @ Return DOT node statement line. -InstallMethod(GV_StringifyNode, "for string and record", -[IsGVNode], -function(node) - local name, attrs; - name := GV_StringifyNodeName(node); - attrs := GraphvizAttrs(node); - return StringFormatted("\t{}{}\n", name, GV_StringifyNodeEdgeAttrs(attrs)); -end); - -# @ Return DOT graph edge statement line. -BindGlobal("GV_StringifyEdge", -function(edge, edge_str) - local head, tail, attrs; - Assert(0, IsGVEdge(edge)); - Assert(0, IsString(edge_str)); - head := GV_StringifyNodeName(GraphvizHead(edge)); - tail := GV_StringifyNodeName(GraphvizTail(edge)); - attrs := GraphvizAttrs(edge); - - # handle : syntax - return StringFormatted("\t{} {} {}{}\n", - head, - edge_str, - tail, - GV_StringifyNodeEdgeAttrs(attrs)); -end); - -InstallMethod(GV_StringifyGraphAttrs, -"for a graphviz graph", -[IsGVGraph], -function(graph) - local result, attrs, kv; - attrs := GraphvizAttrs(graph); - result := ""; - - if Length(attrs) <> 0 then - Append(result, "\t"); - for kv in attrs do - Append(result, - StringFormatted("{} ", kv)); - od; - Append(result, "\n"); - fi; - return result; -end); - -InstallMethod(GV_StringifyNodeEdgeAttrs, -"for a GV_Map", -[IsGV_Map], -function(attrs) - local result, keys, key, val, n, i, tmp; - - result := ""; - n := Length(GV_MapNames(attrs)); - keys := SSortedList(GV_MapNames(attrs)); - - if n <> 0 then - Append(result, " ["); - for i in [1 .. n - 1] do - key := keys[i]; - val := attrs[key]; - - tmp := Chomp(val); - if "label" = key and StartsWith(tmp, "<<") and EndsWith(tmp, ">>") then - val := StringFormatted("{}", val); - else - if ' ' in key then - key := StringFormatted("\"{}\"", key); - fi; - if ' ' in val or '>' in val or '^' in val or '#' in val then - # TODO avoid code duplication here, and below - val := StringFormatted("\"{}\"", val); - fi; - fi; - - Append(result, - StringFormatted("{}={}, ", - key, - val)); - od; - - # handle last element - key := keys[n]; - val := attrs[key]; - - tmp := Chomp(val); - if "label" = key and StartsWith(tmp, "<<") and EndsWith(tmp, ">>") then - val := StringFormatted("{}", val); - else - if ' ' in key then - key := StringFormatted("\"{}\"", key); - fi; - if ' ' in val or '>' in val or '^' in val or '#' in val then - # TODO what are the allowed things in the value? - val := StringFormatted("\"{}\"", val); - fi; - fi; - - Append(result, - StringFormatted("{}={}]", - key, - val)); - fi; - - return result; -end); - -DeclareOperation("GV_GetIdx", [IsGVObject]); -InstallMethod(GV_GetIdx, -"for a graphviz object", -[IsGVObject], -x -> x!.Idx); - -DeclareOperation("GV_ConstructHistory", [IsGVGraph]); -InstallMethod(GV_ConstructHistory, -"for a graphviz graph", -[IsGVGraph], -function(graph) - local nodes, edges, subs, node_hist, edge_hist, subs_hist, hist; - - nodes := GraphvizNodes(graph); - edges := GraphvizEdges(graph); - subs := GraphvizSubgraphs(graph); - - node_hist := List(GV_MapNames(nodes), n -> [GV_GetIdx(nodes[n]), nodes[n]]); - subs_hist := List(GV_MapNames(subs), s -> [GV_GetIdx(subs[s]), subs[s]]); - edge_hist := List(edges, e -> [GV_GetIdx(e), e]); - - hist := Concatenation(node_hist, edge_hist, subs_hist); - SortBy(hist, v -> v[1]); - - Apply(hist, x -> x[2]); - return hist; -end); - -InstallMethod(GV_StringifyGraph, -"for a graphviz graph and a string", -[IsGVGraph, IsBool], -function(graph, is_subgraph) - local result, obj; - result := ""; - - # get the correct head to use - if is_subgraph then - if IsGVContext(graph) then - Append(result, GV_StringifyContextHead(graph)); - elif IsGVGraph(graph) or IsGVDigraph(graph) then - Append(result, GV_StringifySubgraphHead(graph)); - else - ErrorNoReturn("Invalid subgraph type."); - fi; - elif IsGVDigraph(graph) then - Append(result, "//dot\n"); - Append(result, GV_StringifyDigraphHead(graph)); - elif IsGVGraph(graph) then - Append(result, "//dot\n"); - Append(result, GV_StringifyGraphHead(graph)); - elif IsGVContext(graph) then - Append(result, "//dot\n"); - Append(result, GV_StringifyContextHead(graph)); - else - ErrorNoReturn("Invalid graph type."); - fi; - - Append(result, GV_StringifyGraphAttrs(graph)); - - # Add child graphviz objects - for obj in GV_ConstructHistory(graph) do - if IsGVGraph(obj) then - Append(result, GV_StringifyGraph(obj, true)); - elif IsGVNode(obj) then - Append(result, GV_StringifyNode(obj)); - elif IsGVEdge(obj) then - if IsGVDigraph(GV_GetRoot(graph)) then - Append(result, GV_StringifyEdge(obj, "->")); - else - Append(result, GV_StringifyEdge(obj, "--")); - fi; - else - ErrorNoReturn("Invalid graphviz object type."); - fi; - od; - - if IsGVContext(graph) then - # reset attributes following the context - if GV_GetParent(graph) <> fail then - Append(result, GV_StringifyGraphAttrs(GV_GetParent(graph))); - fi; - Append(result, "\n"); - else - Append(result, "}\n"); - fi; - return result; -end); - -InstallMethod(AsString, "for a graphviz graph", -[IsGVGraph], -graph -> GV_StringifyGraph(graph, false)); - -BindGlobal("GV_IsValidRGBColor", -function(str) - local valid, i; - - valid := "0123456789ABCDEFabcdef"; +############################################################################# +# Stringify +############################################################################# - if Length(str) <> 7 or str[1] <> '#' then - return false; - fi; +# It might be more natural for the next method to be one for String rather than +# AsString, but unfortunately String is an attribute, which means it is +# immutable, and hence cannot be changed after it is first set. - for i in [2 .. 7] do - if not str[i] in valid then - return false; - fi; - od; - return true; -end); +InstallMethod(AsString, "for a graphviz (di)graph", +[IsGraphvizGraphDigraphOrContext], graph -> GV_StringifyGraph(graph, false)); -BindGlobal("GV_IsValidColor", -c -> IsString(c) and (GV_IsValidRGBColor(c) or c in GV_ValidColorNames)); +# Can't do the following because it conflicts with the PrintString above, we +# leave this here as a reminder. -BindGlobal("GV_ErrorIfNotValidColor", -function(c) - if not GV_IsValidColor(c) then - if IsString(c) then - c := StringFormatted("\"{}\"", c); - fi; - ErrorFormatted("invalid color {} ({}), ", - "valid colors are RGB values or names from ", - "the GraphViz 2.44.1 X11 Color Scheme", - " http://graphviz.org/doc/info/colors.html", - c, - TNAM_OBJ(c)); - fi; -end); - -BindGlobal("GV_ErrorIfNotNodeColoring", -function(gv, colors) - local N; - N := Size(GraphvizNodes(gv)); - if Length(colors) <> N then - ErrorFormatted( - "the number of node colors must be the same as the number", - " of nodes, expected {} but found {}", N, Length(colors)); - fi; - Perform(colors, GV_ErrorIfNotValidColor); -end); +# InstallMethod(PrintObj, "for a graphviz object with subobjects", +# [IsGraphvizGraphDigraphOrContext], +# function(gv) +# Print(String(gv)); +# end); InstallMethod(GraphvizSetNodeLabels, -"for a graphviz graph and list of colors", -[IsGVGraph, IsList], +"for a graphviz (di)graph or context and list of colors", +[IsGraphvizGraphDigraphOrContext, IsList], function(gv, labels) local nodes, i; - # TODO error if labels and nodes aren't same size + if Size(GraphvizNodes(gv)) <> Size(labels) then + ErrorFormatted("the 2nd argument (list of node labels) ", + "has incorrect length, expected {}, but ", + "found {}", Size(GraphvizNodes(gv)), Size(labels)); + fi; # TODO GV_ErrorIfNotValidLabel nodes := GraphvizNodes(gv); for i in [1 .. Size(nodes)] do @@ -1500,8 +585,8 @@ function(gv, labels) end); InstallMethod(GraphvizSetNodeColors, -"for a graphviz graph and list of colors", -[IsGVGraph, IsList], +"for a graphviz (di)graph or context and list of colors", +[IsGraphvizGraphDigraphOrContext, IsList], function(gv, colors) local nodes, i; @@ -1515,3 +600,17 @@ function(gv, colors) od; return gv; end); + +InstallGlobalFunction(ErrorIfNotValidColor, +function(c) + if not GV_IsValidColor(c) then + if IsString(c) then + c := StringFormatted("\"{}\"", c); + fi; + ErrorFormatted("invalid color {} ({}), valid colors are RGB values ", + "or names from the GraphViz 2.44.1 X11 Color Scheme", + " http://graphviz.org/doc/info/colors.html", + c, + TNAM_OBJ(c)); + fi; +end); diff --git a/gap/error.gd b/gap/error.gd new file mode 100644 index 0000000..56fea6d --- /dev/null +++ b/gap/error.gd @@ -0,0 +1,11 @@ +############################################################################# +## +## error.gd +## Copyright (C) 2024 James Mitchell +## +## Licensing information can be found in the README file of this package. +## +############################################################################# +## + +DeclareGlobalFunction("ErrorFormatted"); diff --git a/gap/error.gi b/gap/error.gi new file mode 100644 index 0000000..605e83c --- /dev/null +++ b/gap/error.gi @@ -0,0 +1,47 @@ +############################################################################# +## +## error.gi +## Copyright (C) 2024 James Mitchell +## +## Licensing information can be found in the README file of this package. +## +############################################################################# +## + +BindGlobal("NumberOfSubstrings", +function(string, substring) + local pos, count; + + pos := 0; + count := 0; + while pos <= Length(string) and pos <> fail do + pos := PositionSublist(string, substring, pos); + if pos <> fail then + count := count + 1; + fi; + od; + return count; +end); + +InstallGlobalFunction(ErrorFormatted, +function(arg...) + local pos, fmt, n, msg; + + pos := PositionProperty(arg, x -> not IsString(x)); + if pos = fail then + pos := Length(arg) + 1; + fi; + fmt := Concatenation(arg{[1 .. pos - 1]}); + n := NumberOfSubstrings(fmt, "{}"); + arg := Concatenation([Concatenation(arg{[1 .. Length(arg) - n]})], + arg{[Length(arg) - n + 1 .. Length(arg)]}); + msg := CallFuncList(StringFormatted, arg); + # RemoveCharacters(msg, "\\\n"); + ErrorInner( + rec(context := ParentLVars(GetCurrentLVars()), + mayReturnVoid := false, + mayReturnObj := false, + lateMessage := "type 'quit;' to quit to outer loop", + printThisStatement := false), + [msg]); +end); diff --git a/gap/gv.gd b/gap/gv.gd new file mode 100644 index 0000000..ddaef86 --- /dev/null +++ b/gap/gv.gd @@ -0,0 +1,226 @@ +############################################################################# +## +## gv.gd +## Copyright (C) 2024 James Mitchell +## +## Licensing information can be found in the README file of this package. +## +############################################################################# +## + +## This file contains declarations of the internal/private functions for the +## graphviz package. + +DeclareOperation("GV_GetCounter", [IsGraphvizGraphDigraphOrContext]); +DeclareOperation("GV_IncCounter", [IsGraphvizGraphDigraphOrContext]); +DeclareCategory("GV_IsMap", IsObject); +DeclareAttribute("Size", GV_IsMap); +DeclareOperation("\[\]", [GV_IsMap, IsObject]); +DeclareOperation("\[\]:=", [GV_IsMap, IsObject, IsObject]); +DeclareOperation("Unbind\[\]", [GV_IsMap, IsObject]); +DeclareOperation("IsBound\[\]", [GV_IsMap, IsObject]); + +DeclareOperation("GV_StringifyGraphHead", [IsGraphvizGraphDigraphOrContext]); +DeclareOperation("GV_StringifyDigraphHead", [IsGraphvizGraphDigraphOrContext]); +DeclareOperation("GV_StringifySubgraphHead", [IsGraphvizGraphDigraphOrContext]); +DeclareOperation("GV_StringifyContextHead", [IsGraphvizGraphDigraphOrContext]); +DeclareOperation("GV_StringifyNode", [IsGraphvizNode]); +DeclareOperation("GV_StringifyGraphAttrs", [IsGraphvizGraphDigraphOrContext]); +DeclareOperation("GV_StringifyNodeEdgeAttrs", [GV_IsMap]); +DeclareOperation("GV_StringifyGraph", [IsGraphvizGraphDigraphOrContext, IsBool]); + +DeclareOperation("GV_FindNode", [IsGraphvizGraphDigraphOrContext, IsObject]); + +DeclareOperation("GV_Pluralize", [IsInt, IsString]); + +DeclareOperation("GV_Node", [IsGraphvizGraphDigraphOrContext, IsString]); +DeclareOperation("GV_Edge", +[IsGraphvizGraphDigraphOrContext, IsGraphvizNode, IsGraphvizNode]); +DeclareOperation("GV_Graph", [IsGraphvizGraphDigraphOrContext, IsString]); +DeclareOperation("GV_Digraph", [IsGraphvizDigraph, IsString]); +DeclareOperation("GV_Context", [IsGraphvizGraphDigraphOrContext, IsString]); +DeclareOperation("GV_Map", []); +DeclareOperation("GV_MapNames", [GV_IsMap]); + +DeclareOperation("GV_HasNode", [IsGraphvizGraphDigraphOrContext, IsObject]); + +DeclareOperation("GV_GetParent", [IsGraphvizGraphDigraphOrContext]); +DeclareOperation("GV_GraphTreeSearch", +[IsGraphvizGraphDigraphOrContext, IsFunction]); +DeclareOperation("GV_FindGraphWithNode", +[IsGraphvizGraphDigraphOrContext, IsString]); +DeclareOperation("GV_GetRoot", [IsGraphvizGraphDigraphOrContext]); +DeclareOperation("GV_EnclosingNonContext", [IsGraphvizGraphDigraphOrContext]); +DeclareOperation("GV_AddNode", +[IsGraphvizGraphDigraphOrContext, IsGraphvizNode]); +DeclareOperation("GV_AddEdge", +[IsGraphvizGraphDigraphOrContext, IsGraphvizEdge]); +DeclareOperation("GV_GetIdx", [IsGraphvizObject]); +DeclareOperation("GV_ConstructHistory", [IsGraphvizGraphDigraphOrContext]); + +DeclareGlobalFunction("GV_IsValidColor"); +DeclareGlobalFunction("GV_ErrorIfNotNodeColoring"); + +# TODO move to dot? and make public? +BindGlobal("GV_ObjectFamily", + NewFamily("GV_ObjectFamily", IsGraphvizObject)); + +# TODO move to dot? and make public? +BindGlobal("GV_DigraphType", NewType(GV_ObjectFamily, + IsGraphvizDigraph and + IsComponentObjectRep and + IsAttributeStoringRep)); + +# TODO move to dot? and make public? +BindGlobal("GV_GraphType", NewType(GV_ObjectFamily, + IsGraphvizGraph and + IsComponentObjectRep and + IsAttributeStoringRep)); + +BindGlobal("GV_KNOWN_ATTRS", [ + "_background", "area", "arrowhead", "arrowsize", "arrowtail", "bb", + "beautify", "bgcolor", "center", "charset", "class", "cluster", "clusterrank", + "color", "colorscheme", "comment", "compound", "concentrate", "constraint", + "Damping", "decorate", "defaultdist", "dim", "dimen", "dir", + "diredgeconstraints", "distortion", "dpi", "edgehref", "edgetarget", + "edgetooltip", "edgeURL", "epsilon", "esep", "fillcolor", "fixedsize", + "fontcolor", "fontname", "fontnames", "fontpath", "fontsize", "forcelabels", + "gradientangle", "group", "head_lp", "headclip", "headhref", "headlabel", + "headport", "headtarget", "headtooltip", "headURL", "height", "href", "id", + "image", "imagepath", "imagepos", "imagescale", "inputscale", "K", "label", + "label_scheme", "labelangle", "labeldistance", "labelfloat", "labelfontcolor", + "labelfontname", "labelfontsize", "labelhref", "labeljust", "labelloc", + "labeltarget", "labeltooltip", "labelURL", "landscape", "layer", + "layerlistsep", "layers", "layerselect", "layersep", "layout", "len", + "levels", "levelsgap", "lhead", "lheight", "linelength", "lp", "ltail", + "lwidth", "margin", "maxiter", "mclimit", "mindist", "minlen", "mode", + "model", "newrank", "nodesep", "nojustify", "normalize", "notranslate", + "nslimit", "nslimit1", "oneblock", "ordering", "orientation", "outputorder", + "overlap", "overlap_scaling", "overlap_shrink", "pack", "packmode", "pad", + "page", "pagedir", "pencolor", "penwidth", "peripheries", "pin", "pos", + "quadtree", "quantum", "rank", "rankdir", "ranksep", "ratio", "rects", + "regular", "remincross", "repulsiveforce", "resolution", "root", "rotate", + "rotation", "samehead", "sametail", "samplepoints", "scale", "searchsize", + "sep", "shape", "shapefile", "showboxes", "sides", "size", "skew", + "smoothing", "sortv", "splines", "start", "style", "stylesheet", "tail_lp", + "tailclip", "tailhref", "taillabel", "tailport", "tailtarget", "tailtooltip", + "tailURL", "target", "TBbalance", "tooltip", "truecolor", "URL", "vertices", + "viewport", "voro_margin", "weight", "width", "xdotversion", "xlabel", "xlp", + "z" +]); + +BindGlobal("GV_ValidColorNames", + ["aliceblue", "antiquewhite", "antiquewhite1", "antiquewhite2", + "antiquewhite3", "antiquewhite4", "aquamarine", "aquamarine1", "aquamarine2", + "aquamarine3", "aquamarine4", "azure", "azure1", "azure2", "azure3", + "azure4", "beige", "bisque", "bisque1", "bisque2", "bisque3", "bisque4", + "black", "blanchedalmond", "blue", "blue1", "blue2", "blue3", "blue4", + "blueviolet", "brown", "brown1", "brown2", "brown3", "brown4", "burlywood", + "burlywood1", "burlywood2", "burlywood3", "burlywood4", "cadetblue", + "cadetblue1", "cadetblue2", "cadetblue3", "cadetblue4", "chartreuse", + "chartreuse1", "chartreuse2", "chartreuse3", "chartreuse4", "chocolate", + "chocolate1", "chocolate2", "chocolate3", "chocolate4", "coral", "coral1", + "coral2", "coral3", "coral4", "cornflowerblue", "cornsilk", "cornsilk1", + "cornsilk2", "cornsilk3", "cornsilk4", "crimson", "cyan", "cyan1", "cyan2", + "cyan3", "cyan4", "darkgoldenrod", "darkgoldenrod1", "darkgoldenrod2", + "darkgoldenrod3", "darkgoldenrod4", "darkgreen", "darkkhaki", + "darkolivegreen", "darkolivegreen1", "darkolivegreen2", "darkolivegreen3", + "darkolivegreen4", "darkorange", "darkorange1", "darkorange2", "darkorange3", + "darkorange4", "darkorchid", "darkorchid1", "darkorchid2", "darkorchid3", + "darkorchid4", "darksalmon", "darkseagreen", "darkseagreen1", + "darkseagreen2", "darkseagreen3", "darkseagreen4", "darkslateblue", + "darkslategray", "darkslategray1", "darkslategray2", "darkslategray3", + "darkslategray4", "darkslategrey", "darkturquoise", "darkviolet", "deeppink", + "deeppink1", "deeppink2", "deeppink3", "deeppink4", "deepskyblue", + "deepskyblue1", "deepskyblue2", "deepskyblue3", "deepskyblue4", "dimgray", + "dimgrey", "dodgerblue", "dodgerblue1", "dodgerblue2", "dodgerblue3", + "dodgerblue4", "firebrick", "firebrick1", "firebrick2", "firebrick3", + "firebrick4", "floralwhite", "forestgreen", "gainsboro", "ghostwhite", + "gold", "gold1", "gold2", "gold3", "gold4", "goldenrod", "goldenrod1", + "goldenrod2", "goldenrod3", "goldenrod4", "gray", "gray0", "gray1", "gray10", + "gray100", "gray11", "gray12", "gray13", "gray14", "gray15", "gray16", + "gray17", "gray18", "gray19", "gray2", "gray20", "gray21", "gray22", + "gray23", "gray24", "gray25", "gray26", "gray27", "gray28", "gray29", + "gray3", "gray30", "gray31", "gray32", "gray33", "gray34", "gray35", + "gray36", "gray37", "gray38", "gray39", "gray4", "gray40", "gray41", + "gray42", "gray43", "gray44", "gray45", "gray46", "gray47", "gray48", + "gray49", "gray5", "gray50", "gray51", "gray52", "gray53", "gray54", + "gray55", "gray56", "gray57", "gray58", "gray59", "gray6", "gray60", + "gray61", "gray62", "gray63", "gray64", "gray65", "gray66", "gray67", + "gray68", "gray69", "gray7", "gray70", "gray71", "gray72", "gray73", + "gray74", "gray75", "gray76", "gray77", "gray78", "gray79", "gray8", + "gray80", "gray81", "gray82", "gray83", "gray84", "gray85", "gray86", + "gray87", "gray88", "gray89", "gray9", "gray90", "gray91", "gray92", + "gray93", "gray94", "gray95", "gray96", "gray97", "gray98", "gray99", + "green", "green1", "green2", "green3", "green4", "greenyellow", "grey", + "grey0", "grey1", "grey10", "grey100", "grey11", "grey12", "grey13", + "grey14", "grey15", "grey16", "grey17", "grey18", "grey19", "grey2", + "grey20", "grey21", "grey22", "grey23", "grey24", "grey25", "grey26", + "grey27", "grey28", "grey29", "grey3", "grey30", "grey31", "grey32", + "grey33", "grey34", "grey35", "grey36", "grey37", "grey38", "grey39", + "grey4", "grey40", "grey41", "grey42", "grey43", "grey44", "grey45", + "grey46", "grey47", "grey48", "grey49", "grey5", "grey50", "grey51", + "grey52", "grey53", "grey54", "grey55", "grey56", "grey57", "grey58", + "grey59", "grey6", "grey60", "grey61", "grey62", "grey63", "grey64", + "grey65", "grey66", "grey67", "grey68", "grey69", "grey7", "grey70", + "grey71", "grey72", "grey73", "grey74", "grey75", "grey76", "grey77", + "grey78", "grey79", "grey8", "grey80", "grey81", "grey82", "grey83", + "grey84", "grey85", "grey86", "grey87", "grey88", "grey89", "grey9", + "grey90", "grey91", "grey92", "grey93", "grey94", "grey95", "grey96", + "grey97", "grey98", "grey99", "honeydew", "honeydew1", "honeydew2", + "honeydew3", "honeydew4", "hotpink", "hotpink1", "hotpink2", "hotpink3", + "hotpink4", "indianred", "indianred1", "indianred2", "indianred3", + "indianred4", "indigo", "invis", "ivory", "ivory1", "ivory2", "ivory3", + "ivory4", "khaki", "khaki1", "khaki2", "khaki3", "khaki4", "lavender", + "lavenderblush", "lavenderblush1", "lavenderblush2", "lavenderblush3", + "lavenderblush4", "lawngreen", "lemonchiffon", "lemonchiffon1", + "lemonchiffon2", "lemonchiffon3", "lemonchiffon4", "lightblue", "lightblue1", + "lightblue2", "lightblue3", "lightblue4", "lightcoral", "lightcyan", + "lightcyan1", "lightcyan2", "lightcyan3", "lightcyan4", "lightgoldenrod", + "lightgoldenrod1", "lightgoldenrod2", "lightgoldenrod3", "lightgoldenrod4", + "lightgoldenrodyellow", "lightgray", "lightgrey", "lightpink", "lightpink1", + "lightpink2", "lightpink3", "lightpink4", "lightsalmon", "lightsalmon1", + "lightsalmon2", "lightsalmon3", "lightsalmon4", "lightseagreen", + "lightskyblue", "lightskyblue1", "lightskyblue2", "lightskyblue3", + "lightskyblue4", "lightslateblue", "lightslategray", "lightslategrey", + "lightsteelblue", "lightsteelblue1", "lightsteelblue2", "lightsteelblue3", + "lightsteelblue4", "lightyellow", "lightyellow1", "lightyellow2", + "lightyellow3", "lightyellow4", "limegreen", "linen", "magenta", "magenta1", + "magenta2", "magenta3", "magenta4", "maroon", "maroon1", "maroon2", + "maroon3", "maroon4", "mediumaquamarine", "mediumblue", "mediumorchid", + "mediumorchid1", "mediumorchid2", "mediumorchid3", "mediumorchid4", + "mediumpurple", "mediumpurple1", "mediumpurple2", "mediumpurple3", + "mediumpurple4", "mediumseagreen", "mediumslateblue", "mediumspringgreen", + "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", + "mistyrose", "mistyrose1", "mistyrose2", "mistyrose3", "mistyrose4", + "moccasin", "navajowhite", "navajowhite1", "navajowhite2", "navajowhite3", + "navajowhite4", "navy", "navyblue", "none", "oldlace", "olivedrab", + "olivedrab1", "olivedrab2", "olivedrab3", "olivedrab4", "orange", "orange1", + "orange2", "orange3", "orange4", "orangered", "orangered1", "orangered2", + "orangered3", "orangered4", "orchid", "orchid1", "orchid2", "orchid3", + "orchid4", "palegoldenrod", "palegreen", "palegreen1", "palegreen2", + "palegreen3", "palegreen4", "paleturquoise", "paleturquoise1", + "paleturquoise2", "paleturquoise3", "paleturquoise4", "palevioletred", + "palevioletred1", "palevioletred2", "palevioletred3", "palevioletred4", + "papayawhip", "peachpuff", "peachpuff1", "peachpuff2", "peachpuff3", + "peachpuff4", "peru", "pink", "pink1", "pink2", "pink3", "pink4", "plum", + "plum1", "plum2", "plum3", "plum4", "powderblue", "purple", "purple1", + "purple2", "purple3", "purple4", "red", "red1", "red2", "red3", "red4", + "rosybrown", "rosybrown1", "rosybrown2", "rosybrown3", "rosybrown4", + "royalblue", "royalblue1", "royalblue2", "royalblue3", "royalblue4", + "saddlebrown", "salmon", "salmon1", "salmon2", "salmon3", "salmon4", + "sandybrown", "seagreen", "seagreen1", "seagreen2", "seagreen3", "seagreen4", + "seashell", "seashell1", "seashell2", "seashell3", "seashell4", "sienna", + "sienna1", "sienna2", "sienna3", "sienna4", "skyblue", "skyblue1", + "skyblue2", "skyblue3", "skyblue4", "slateblue", "slateblue1", "slateblue2", + "slateblue3", "slateblue4", "slategray", "slategray1", "slategray2", + "slategray3", "slategray4", "slategrey", "snow", "snow1", "snow2", "snow3", + "snow4", "springgreen", "springgreen1", "springgreen2", "springgreen3", + "springgreen4", "steelblue", "steelblue1", "steelblue2", "steelblue3", + "steelblue4", "tan", "tan1", "tan2", "tan3", "tan4", "thistle", "thistle1", + "thistle2", "thistle3", "thistle4", "tomato", "tomato1", "tomato2", + "tomato3", "tomato4", "transparent", "turquoise", "turquoise1", "turquoise2", + "turquoise3", "turquoise4", "violet", "violetred", "violetred1", + "violetred2", "violetred3", "violetred4", "wheat", "wheat1", "wheat2", + "wheat3", "wheat4", "white", "whitesmoke", "yellow", "yellow1", "yellow2", + "yellow3", "yellow4", "yellowgreen"]); diff --git a/gap/gv.gi b/gap/gv.gi new file mode 100644 index 0000000..3c56d6a --- /dev/null +++ b/gap/gv.gi @@ -0,0 +1,691 @@ +############################################################################# +## +## gv.gi +## Copyright (C) 2024 James Mitchell +## +## Licensing information can be found in the README file of this package. +## +############################################################################# +## + +# Code from the GAP standard library, repeated here so that the package works +# with GAP 4.11 since Pluralize was introduced to the GAP library in 4.12 +if CompareVersionNumbers(GAPInfo.Version, "4.12") then + InstallMethod(GV_Pluralize, + "for an integer and a string", + [IsInt, IsString], Pluralize); +else + InstallMethod(GV_Pluralize, + "for an integer and a string", + [IsInt, IsString], + function(args...) + local nargs, i, count, include_num, str, len, out; + + nargs := Length(args); + if nargs >= 1 and IsInt(args[1]) and args[1] >= 0 then + i := 2; + count := args[1]; + include_num := true; + else + i := 1; + include_num := false; # if not given, assume pluralization is wanted. + fi; + + if not (nargs in [i, i + 1] and + IsString(args[i]) and + (nargs = i or IsString(args[i + 1]))) then + ErrorNoReturn("Usage: GV_Pluralize([, ][, ])"); + fi; + + str := args[i]; + len := Length(str); + + if len = 0 then + ErrorNoReturn("the argument must be a non-empty string"); + elif include_num and count = 1 then # no pluralization needed + return Concatenation("\>1\< ", str); + elif nargs = i + 1 then # pluralization given + out := args[i + 1]; + elif len <= 2 then + out := Concatenation(str, "s"); + + # Guess and return the plural form of . + # Inspired by the "Ruby on Rails" inflection rules. + + # Uncountable nouns + elif str in ["equipment", "information"] then + out := str; + + # Irregular plurals + elif str = "axis" then + out := "axes"; + elif str = "child" then + out := "children"; + elif str = "person" then + out := "people"; + + # Peculiar endings + elif EndsWith(str, "ix") or EndsWith(str, "ex") then + out := Concatenation(str{[1 .. len - 2]}, "ices"); + elif EndsWith(str, "x") then + out := Concatenation(str, "es"); + elif EndsWith(str, "tum") or EndsWith(str, "ium") then + out := Concatenation(str{[1 .. len - 2]}, "a"); + elif EndsWith(str, "sis") then + out := Concatenation(str{[1 .. len - 3]}, "ses"); + elif EndsWith(str, "fe") and not EndsWith(str, "ffe") then + out := Concatenation(str{[1 .. len - 2]}, "ves"); + elif EndsWith(str, "lf") or EndsWith(str, "rf") or EndsWith(str, "loaf") then + out := Concatenation(str{[1 .. len - 1]}, "ves"); + elif EndsWith(str, "y") and not str[len - 1] in "aeiouy" then + out := Concatenation(str{[1 .. len - 1]}, "ies"); + elif str{[len - 1, len]} in ["ch", "ss", "sh"] then + out := Concatenation(str, "es"); + elif EndsWith(str, "s") then + out := str; + + # Default to appending 's' + else + out := Concatenation(str, "s"); + fi; + + if include_num then + return Concatenation("\>", String(args[1]), "\< ", out); + fi; + return out; + end); +fi; + +############################################################################### +# Family + type +############################################################################### + +BindGlobal("GV_MapType", NewType(GV_ObjectFamily, + GV_IsMap and + IsComponentObjectRep and + IsAttributeStoringRep)); + +BindGlobal("GV_NodeType", NewType(GV_ObjectFamily, + IsGraphvizNode and + IsComponentObjectRep and + IsAttributeStoringRep)); + +BindGlobal("GV_EdgeType", NewType(GV_ObjectFamily, + IsGraphvizEdge and + IsComponentObjectRep and + IsAttributeStoringRep)); + +BindGlobal("GV_ContextType", NewType(GV_ObjectFamily, + IsGraphvizContext and + IsComponentObjectRep and + IsAttributeStoringRep)); + +############################################################################### +# Constructors etc +############################################################################### + +InstallMethod(GV_Map, "for no args", +[], {} -> Objectify(GV_MapType, rec(Data := rec()))); + +InstallMethod(GV_Node, "for a string", +[IsGraphvizGraphDigraphOrContext, IsString], +function(graph, name) + local out; + if Length(name) = 0 then + ErrorNoReturn("the 2nd argument (string/node name) cannot be empty"); + fi; + out := Objectify(GV_NodeType, + rec( + Name := name, + Attrs := GV_Map(), + Idx := GV_GetCounter(graph))); + GV_IncCounter(graph); + return out; +end); + +InstallMethod(GV_Edge, "for two graphviz nodes", +[IsGraphvizGraphDigraphOrContext, IsGraphvizNode, IsGraphvizNode], +function(graph, head, tail) + local out; + out := Objectify(GV_EdgeType, + rec( + Name := "", + Head := head, + Tail := tail, + Attrs := GV_Map(), + Idx := GV_GetCounter(graph))); + GV_IncCounter(graph); + return out; +end); + +# Graph constructors + +InstallMethod(GV_Digraph, +"for a graphviz digraph and a string", +[IsGraphvizDigraph, IsString], +function(parent, name) + local out; + + out := GraphvizDigraph(name); + out!.Parent := parent; + out!.Idx := GV_GetCounter(parent); + + GV_IncCounter(parent); + return out; +end); + +InstallMethod(GV_Graph, +"for a graphviz graph and a string", +[IsGraphvizGraphDigraphOrContext, IsString], +function(parent, name) + local out; + + out := GraphvizGraph(name); + out!.Parent := parent; + out!.Idx := GV_GetCounter(parent); + + GV_IncCounter(parent); + return out; +end); + +InstallMethod(GV_Context, +"for a string and a positive integer", +[IsGraphvizGraphDigraphOrContext, IsString], +function(parent, name) + local out; + + out := Objectify(GV_ContextType, + rec( + Name := name, + Subgraphs := GV_Map(), + Nodes := GV_Map(), + Edges := [], + Attrs := [], + Parent := parent, + Idx := GV_GetCounter(parent), + Counter := 1)); + + GV_IncCounter(parent); + return out; +end); + +############################################################ +# Graphviz Map Functions +############################################################ + +InstallMethod(\[\], +"for a graphviz map and a string", +[GV_IsMap, IsString], +function(m, o) + if IsBound(m[o]) then + return m!.Data.(o); + fi; + return fail; +end); + +InstallMethod(\[\], +"for a graphviz map and an object", +[GV_IsMap, IsObject], +{m, o} -> m[String(o)]); + +InstallMethod(\[\]\:\=, +"for a graphviz map and two objects", +[GV_IsMap, IsObject, IsObject], +function(m, key, val) + m!.Data.(key) := val; +end); + +InstallMethod(Unbind\[\], +"for a graphviz map and an object", +[GV_IsMap, IsObject], +function(m, key) + Unbind(m!.Data.(key)); +end); + +InstallMethod(IsBound\[\], +"for a graphviz map and an object", +[GV_IsMap, IsObject], +{m, key} -> IsBound(m!.Data.(key))); + +InstallMethod(GV_MapNames, "for a graphviz map", +[GV_IsMap], m -> RecNames(m!.Data)); + +InstallMethod(ViewObj, "for a graphviz map", [GV_IsMap], +function(m) + ViewObj(m!.Data); +end); + +InstallMethod(Size, "for a graphviz map", +[GV_IsMap], m -> Length(GV_MapNames(m))); + +# ?? + +InstallMethod(GV_IncCounter, +"for a graphviz graph", +[IsGraphvizGraphDigraphOrContext], +function(x) + x!.Counter := x!.Counter + 1; +end); + +InstallMethod(GV_GetCounter, "for a graphviz graph", +[IsGraphvizGraphDigraphOrContext], +x -> x!.Counter); + +# Nodes + +InstallMethod(GV_HasNode, +"for a graphviz graph", +[IsGraphvizGraphDigraphOrContext, IsString], +{g, name} -> name in GV_MapNames(GraphvizNodes(g))); + +InstallMethod(GV_GetParent, +"for a graphviz graph", +[IsGraphvizGraphDigraphOrContext], graph -> graph!.Parent); + +InstallMethod(GV_GraphTreeSearch, +"for a graphviz graph and a predicate", +[IsGraphvizGraphDigraphOrContext, IsFunction], +function(graph, pred) + local seen, to_visit, g, key, subgraph, parent; + seen := [graph]; + to_visit := [graph]; + + while Length(to_visit) > 0 do + g := Remove(to_visit, Length(to_visit)); + + # Check this graph + if pred(g) then + return g; + fi; + + # add subgraphs to list of to visit if not visited + for key in GV_MapNames(GraphvizSubgraphs(g)) do + subgraph := GraphvizSubgraphs(g)[key]; + if not ForAny(seen, s -> IsIdenticalObj(s, subgraph)) then + Add(seen, subgraph); + Add(to_visit, subgraph); + fi; + od; + + # add parent if not visited + parent := GV_GetParent(g); + if not IsGraphvizGraphDigraphOrContext(parent) then + continue; + fi; + if not ForAny(seen, s -> IsIdenticalObj(s, parent)) then + Add(seen, parent); + Add(to_visit, parent); + fi; + od; + + return fail; +end); + +InstallMethod(GV_FindGraphWithNode, +"for a graphviz graph and a node", +[IsGraphvizGraphDigraphOrContext, IsString], +{g, n} -> GV_GraphTreeSearch(g, v -> v[n] <> fail)); + +InstallMethod(GV_GetRoot, +"for a graphviz graph", +[IsGraphvizGraphDigraphOrContext], +function(graph) + while GV_GetParent(graph) <> fail do + graph := GV_GetParent(graph); + od; + return graph; +end); + +InstallMethod(GV_EnclosingNonContext, +"for a graphviz object with subobjects", +[IsGraphvizContext], +function(graph) + local parent; + + repeat + parent := GV_GetParent(graph); + until parent = fail or not IsGraphvizContext(parent); + return parent; +end); + +InstallMethod(GV_FindNode, +"for a graphviz graph and a string", +[IsGraphvizGraphDigraphOrContext, IsString], +function(g, n) + local graph; + graph := GV_FindGraphWithNode(g, n); + if graph = fail then + return graph; + fi; + return graph[n]; +end); + +InstallMethod(GV_AddNode, +"for a graphviz graph and node", +[IsGraphvizGraphDigraphOrContext, IsGraphvizNode], +function(x, node) + local name, nodes, found; + + name := GraphvizName(node); + nodes := GraphvizNodes(x); + + # dont add if already node with the same name + found := GV_FindGraphWithNode(x, name); + if found <> fail then + ErrorFormatted("the 2nd argument (node) has name \"{}\"", + " but there is already a node with this name in ", + "the 1st argument (a graphviz (di)graph / context)", + " named \"{}\"", + name, + GraphvizName(x)); + fi; + + nodes[name] := node; + return x; +end); + +InstallMethod(GV_AddEdge, +"for a graphviz graph and edge", +[IsGraphvizGraphDigraphOrContext, IsGraphvizEdge], +function(x, edge) + local head, tail, head_name, tail_name, hg, tg; + + head := GraphvizHead(edge); + tail := GraphvizTail(edge); + head_name := GraphvizName(head); + tail_name := GraphvizName(tail); + hg := GV_FindGraphWithNode(x, head_name); + tg := GV_FindGraphWithNode(x, tail_name); + + # make sure the nodes exist / are the same as existing ones + if hg <> fail and head <> hg[head_name] then + ErrorFormatted("The 2nd argument (edge) has head node named \"{}\"", + " but there is already a node with this name in ", + "the 1st argument (a graphviz (di)graph / context)", + " named \"{}\"", + head_name, + GraphvizName(x)); + fi; + if tg <> fail and tail <> tg[tail_name] then + ErrorFormatted("The 2nd argument (edge) has tail node named \"{}\"", + " but there is already a node with this name in ", + "the 1st argument (a graphviz (di)graph / context)", + " named \"{}\"", + head_name, + GraphvizName(x)); + fi; + + Add(x!.Edges, edge); + return x; +end); + +############################################################################### +# Stringifying +############################################################################### + +# @ Return DOT graph head line. +InstallMethod(GV_StringifyGraphHead, "for a string", +[IsGraphvizGraphDigraphOrContext], +graph -> StringFormatted("graph {} {{\n", GraphvizName(graph))); + +# @ Return DOT digraph head line. +InstallMethod(GV_StringifyDigraphHead, "for a string", [IsGraphvizDigraph], +graph -> StringFormatted("digraph {} {{\n", GraphvizName(graph))); + +# @ Return DOT subgraph head line. +InstallMethod(GV_StringifySubgraphHead, "for a string", +[IsGraphvizGraphDigraphOrContext], +graph -> StringFormatted("subgraph {} {{\n", GraphvizName(graph))); + +# @ Return DOT subgraph head line. +InstallMethod(GV_StringifyContextHead, "for a string", [IsGraphvizContext], +graph -> StringFormatted("// {} context \n", GraphvizName(graph))); + +BindGlobal("GV_StringifyNodeName", +function(node) + local name, old; + + Assert(0, IsGraphvizNode(node)); + name := GraphvizName(node); + if (ForAny("- .+", x -> x in name) + or (IsDigitChar(First(name)) and IsAlphaChar(Last(name)))) + and not StartsWith(name, "\"") then + old := name; + name := StringFormatted("\"{}\"", name); + Info(InfoWarning, + 1, + "invalid node name ", + old, + " using ", + name, + " instead"); + fi; + return name; +end); + +# @ Return DOT node statement line. +InstallMethod(GV_StringifyNode, "for string and record", +[IsGraphvizNode], +function(node) + local name, attrs; + name := GV_StringifyNodeName(node); + attrs := GraphvizAttrs(node); + return StringFormatted("\t{}{}\n", name, GV_StringifyNodeEdgeAttrs(attrs)); +end); + +# @ Return DOT graph edge statement line. +BindGlobal("GV_StringifyEdge", +function(edge, edge_str) + local head, tail, attrs; + Assert(0, IsGraphvizEdge(edge)); + Assert(0, IsString(edge_str)); + head := GV_StringifyNodeName(GraphvizHead(edge)); + tail := GV_StringifyNodeName(GraphvizTail(edge)); + attrs := GraphvizAttrs(edge); + + # handle : syntax + return StringFormatted("\t{} {} {}{}\n", + head, + edge_str, + tail, + GV_StringifyNodeEdgeAttrs(attrs)); +end); + +InstallMethod(GV_StringifyGraphAttrs, +"for a graphviz graph", +[IsGraphvizGraphDigraphOrContext], +function(graph) + local result, attrs, kv; + attrs := GraphvizAttrs(graph); + result := ""; + + if Length(attrs) <> 0 then + Append(result, "\t"); + for kv in attrs do + Append(result, + StringFormatted("{} ", kv)); + od; + Append(result, "\n"); + fi; + return result; +end); + +InstallMethod(GV_StringifyNodeEdgeAttrs, +"for a GV_Map", +[GV_IsMap], +function(attrs) + local result, keys, key, val, n, i, tmp; + + result := ""; + n := Length(GV_MapNames(attrs)); + keys := SSortedList(GV_MapNames(attrs)); + + if n <> 0 then + Append(result, " ["); + for i in [1 .. n - 1] do + key := keys[i]; + val := attrs[key]; + + tmp := Chomp(val); + if "label" = key and StartsWith(tmp, "<<") and EndsWith(tmp, ">>") then + val := StringFormatted("{}", val); + else + # TODO it doesn't seem to be possible to enter the if-statement + # below, even with examples where the key contains spaces (probably + # the quotes are added somewhere else). Either uncomment or delete + # this code. + # if ' ' in key then + # key := StringFormatted("\"{}\"", key); + # fi; + if ' ' in val or '>' in val or '^' in val or '#' in val then + # TODO avoid code duplication here, and below + val := StringFormatted("\"{}\"", val); + fi; + fi; + + Append(result, + StringFormatted("{}={}, ", + key, + val)); + od; + + # handle last element + key := keys[n]; + val := attrs[key]; + + tmp := Chomp(val); + if "label" = key and StartsWith(tmp, "<<") and EndsWith(tmp, ">>") then + val := StringFormatted("{}", val); + else + if ' ' in key then + key := StringFormatted("\"{}\"", key); + fi; + if ' ' in val or '>' in val or '^' in val or '#' in val then + # TODO what are the allowed things in the value? + val := StringFormatted("\"{}\"", val); + fi; + fi; + + Append(result, + StringFormatted("{}={}]", + key, + val)); + fi; + + return result; +end); + +InstallMethod(GV_GetIdx, +"for a graphviz object", +[IsGraphvizObject], +x -> x!.Idx); + +InstallMethod(GV_ConstructHistory, +"for a graphviz graph", +[IsGraphvizGraphDigraphOrContext], +function(graph) + local nodes, edges, subs, node_hist, edge_hist, subs_hist, hist; + + nodes := GraphvizNodes(graph); + edges := GraphvizEdges(graph); + subs := GraphvizSubgraphs(graph); + + node_hist := List(GV_MapNames(nodes), n -> [GV_GetIdx(nodes[n]), nodes[n]]); + subs_hist := List(GV_MapNames(subs), s -> [GV_GetIdx(subs[s]), subs[s]]); + edge_hist := List(edges, e -> [GV_GetIdx(e), e]); + + hist := Concatenation(node_hist, edge_hist, subs_hist); + SortBy(hist, v -> v[1]); + + Apply(hist, x -> x[2]); + return hist; +end); + +InstallMethod(GV_StringifyGraph, +"for a graphviz graph and a string", +[IsGraphvizGraphDigraphOrContext, IsBool], +function(graph, is_subgraph) + local result, obj; + result := ""; + + # get the correct head to use + if is_subgraph then + if IsGraphvizContext(graph) then + Append(result, GV_StringifyContextHead(graph)); + else + Append(result, GV_StringifySubgraphHead(graph)); + fi; + elif IsGraphvizDigraph(graph) then + Append(result, "//dot\n"); + Append(result, GV_StringifyDigraphHead(graph)); + elif IsGraphvizGraph(graph) then + Append(result, "//dot\n"); + Append(result, GV_StringifyGraphHead(graph)); + # TODO doesn't seem to be possible to reach the case below either, uncomment + # or delete + # else + # Append(result, "//dot\n"); + # Append(result, GV_StringifyContextHead(graph)); + fi; + + Append(result, GV_StringifyGraphAttrs(graph)); + + # Add child graphviz objects + for obj in GV_ConstructHistory(graph) do + if IsGraphvizGraphDigraphOrContext(obj) then + Append(result, GV_StringifyGraph(obj, true)); + elif IsGraphvizNode(obj) then + Append(result, GV_StringifyNode(obj)); + elif IsGraphvizEdge(obj) then + if IsGraphvizDigraph(GV_GetRoot(graph)) then + Append(result, GV_StringifyEdge(obj, "->")); + else + Append(result, GV_StringifyEdge(obj, "--")); + fi; + fi; + od; + + if IsGraphvizContext(graph) then + # reset attributes following the context + if GV_GetParent(graph) <> fail then + Append(result, GV_StringifyGraphAttrs(GV_GetParent(graph))); + fi; + Append(result, "\n"); + else + Append(result, "}\n"); + fi; + return result; +end); + +BindGlobal("GV_IsValidRGBColor", +function(str) + local valid, i; + + valid := "0123456789ABCDEFabcdef"; + + if Length(str) <> 7 or str[1] <> '#' then + return false; + fi; + + for i in [2 .. 7] do + if not str[i] in valid then + return false; + fi; + od; + return true; +end); + +InstallGlobalFunction(GV_IsValidColor, +c -> IsString(c) and (GV_IsValidRGBColor(c) or c in GV_ValidColorNames)); + +InstallGlobalFunction(GV_ErrorIfNotNodeColoring, +function(gv, colors) + local N; + N := Size(GraphvizNodes(gv)); + if Length(colors) <> N then + ErrorFormatted( + "the number of node colors must be the same as the number", + " of nodes, expected {} but found {}", N, Length(colors)); + fi; + Perform(colors, ErrorIfNotValidColor); +end); diff --git a/gap/splash.gd b/gap/splash.gd new file mode 100644 index 0000000..919a68a --- /dev/null +++ b/gap/splash.gd @@ -0,0 +1,15 @@ +############################################################################# +## +## splash.gd +## Copyright (C) 2024 Matthew Pancer +## +## Licensing information can be found in the README file of this package. +## +############################################################################# +## + +# This function is transplanted from the Digraphs package to here. The original +# function was written by A. Egri-Nagy and Manuel Delgado, with some minor +# modifications by J. Mitchell. + +DeclareGlobalFunction("Splash"); diff --git a/gap/splash.gi b/gap/splash.gi index 4effacb..9d3c67e 100644 --- a/gap/splash.gi +++ b/gap/splash.gi @@ -1,118 +1,133 @@ -# TODO file header -# TODO rename file to remove "i" suffix +############################################################################# +## +## splash.gi +## Copyright (C) 2024 Matthew Pancer +## +## Licensing information can be found in the README file of this package. +## +############################################################################# +## -if not IsBound(Splash) then - BindGlobal("VizViewers", - ["xpdf", "xdg-open", "open", "evince", "okular", "gv"]); +# The contents of this file are transplanted from the Digraphs package to here. +# The original function was written by A. Egri-Nagy and Manuel Delgado, with +# some minor modifications by J. Mitchell. - BindGlobal("Splash", - function(arg...) - local str, opt, path, dir, tdir, file, viewer, type, inn, filetype, out, - engine; +BindGlobal("VizViewers", + ["xpdf", "xdg-open", "open", "evince", "okular", "gv"]); - if IsEmpty(arg) then - ErrorNoReturn("the must be at least 1 argument, found none"); - elif not IsString(arg[1]) and not IsGVGraph(arg[1]) then - ErrorFormatted("the 1st argument must be a string or ", - "graphviz graph, found {}", TNAM_OBJ(arg[1])); - elif IsGVGraph(arg[1]) then - arg[1] := AsString(arg[1]); - fi; - str := arg[1]; +InstallGlobalFunction(Splash, +function(arg...) + local file, str, opt, path, dir, tdir, viewer, type, inn, filetype, out, + engine, i; - opt := rec(); - if IsBound(arg[2]) and IsRecord(arg[2]) then - opt := arg[2]; - elif IsBound(arg[2]) then - ErrorNoReturn("the 2nd argument must be a record,"); - fi; + if IsEmpty(arg) then + ErrorNoReturn("the must be at least 1 argument, found none"); + elif not IsString(arg[1]) and not IsGraphvizGraphDigraphOrContext(arg[1]) then + ErrorFormatted("the 1st argument must be a string or ", + "graphviz graph, found {}", TNAM_OBJ(arg[1])); + elif IsGraphvizGraphDigraphOrContext(arg[1]) then + file := GraphvizName(arg[1]); + for i in [1 .. Length(file)] do + if not IsAlphaChar(file[i]) and not IsDigitChar(file[i]) then + file[i] := '_'; + fi; + od; + arg[1] := String(arg[1]); + else + file := "vizpicture"; + fi; + str := arg[1]; - path := UserHomeExpand("~/"); - if IsBound(opt.path) then - path := opt.path; - fi; + opt := rec(); + if IsBound(arg[2]) and IsRecord(arg[2]) then + opt := arg[2]; + elif IsBound(arg[2]) then + ErrorNoReturn("the 2nd argument must be a record,"); + fi; - if IsBound(opt.directory) then - if not opt.directory in DirectoryContents(path) then - Exec(Concatenation("mkdir ", path, opt.directory)); - fi; - dir := Concatenation(path, opt.directory, "/"); - elif IsBound(opt.path) then - if not "tmp.viz" in DirectoryContents(path) then - tdir := Directory(Concatenation(path, "/", "tmp.viz")); - dir := Filename(tdir, ""); - fi; - else - tdir := DirectoryTemporary(); - dir := Filename(tdir, ""); - fi; + path := UserHomeExpand("~/"); + if IsBound(opt.path) then + path := opt.path; + fi; - # TODO use the graphs name - file := "vizpicture"; - if IsBound(opt.filename) then - file := opt.filename; + if IsBound(opt.directory) then + if not opt.directory in DirectoryContents(path) then + Exec(Concatenation("mkdir ", path, opt.directory)); fi; - - if IsBound(opt.viewer) then - viewer := opt.viewer; - if not IsString(viewer) then - ErrorNoReturn("the option `viewer` must be a string, not an ", - TNAM_OBJ(viewer)); - elif Filename(DirectoriesSystemPrograms(), viewer) = fail then - ErrorNoReturn("the viewer \"", viewer, "\" specified in the option ", - "`viewer` is not available"); - fi; - else - viewer := First(VizViewers, x -> - Filename(DirectoriesSystemPrograms(), x) <> fail); - if viewer = fail then - ErrorNoReturn("none of the default viewers ", VizViewers, - " is available, please specify an available viewer", - " in the options record component `viewer`,"); - fi; + dir := Concatenation(path, opt.directory, "/"); + elif IsBound(opt.path) then + if not "tmp.viz" in DirectoryContents(path) then + tdir := Directory(Concatenation(path, "/", "tmp.viz")); + dir := Filename(tdir, ""); fi; + else + tdir := DirectoryTemporary(); + dir := Filename(tdir, ""); + fi; + + if IsBound(opt.filename) then + file := opt.filename; + fi; - if IsBound(opt.type) and (opt.type = "latex" or opt.type = "dot") then - type := opt.type; - elif Length(str) >= 6 and str{[1 .. 6]} = "%latex" then - type := "latex"; - elif Length(str) >= 5 and str{[1 .. 5]} = "//dot" then - type := "dot"; - else - ErrorNoReturn("the component \"type\" of the 2nd argument ", - " must be \"dot\" or \"latex\","); + if IsBound(opt.viewer) then + viewer := opt.viewer; + if not IsString(viewer) then + ErrorNoReturn("the option `viewer` must be a string, not an ", + TNAM_OBJ(viewer)); + elif Filename(DirectoriesSystemPrograms(), viewer) = fail then + ErrorNoReturn("the viewer \"", viewer, "\" specified in the option ", + "`viewer` is not available"); fi; - if type = "latex" then - inn := Concatenation(dir, file, ".tex"); - else - inn := Concatenation(dir, file, ".dot"); + else + viewer := First(VizViewers, x -> + Filename(DirectoriesSystemPrograms(), x) <> fail); + if viewer = fail then + ErrorNoReturn("none of the default viewers ", VizViewers, + " is available, please specify an available viewer", + " in the options record component `viewer`,"); fi; + fi; - filetype := "pdf"; - if IsBound(opt.filetype) and IsString(opt.filetype) and type <> "latex" then - filetype := opt.filetype; - fi; - out := Concatenation(dir, file, ".", filetype); + if IsBound(opt.type) and (opt.type = "latex" or opt.type = "dot") then + type := opt.type; + elif Length(str) >= 6 and str{[1 .. 6]} = "%latex" then + type := "latex"; + elif Length(str) >= 5 and str{[1 .. 5]} = "//dot" then + type := "dot"; + else + ErrorNoReturn("the component \"type\" of the 2nd argument ", + " must be \"dot\" or \"latex\","); + fi; + if type = "latex" then + inn := Concatenation(dir, file, ".tex"); + else + inn := Concatenation(dir, file, ".dot"); + fi; - engine := "dot"; - if IsBound(opt.engine) then - engine := opt.engine; - if not engine in ["dot", "neato", "twopi", "circo", - "fdp", "sfdp", "patchwork"] then - ErrorNoReturn("the component \"engine\" of the 2nd argument ", - " must be one of: \"dot\", \"neato\", ", - "\"twopi\", \"circo\", \"fdp\", \"sfdp\", ", - "or \"patchwork\""); - fi; - fi; + filetype := "pdf"; + if IsBound(opt.filetype) and IsString(opt.filetype) and type <> "latex" then + filetype := opt.filetype; + fi; + out := Concatenation(dir, file, ".", filetype); - FileString(inn, str); - if type = "latex" then - Exec(Concatenation("cd ", dir, ";", - "pdflatex ", file, " 2>/dev/null 1>/dev/null")); - else - Exec(Concatenation(engine, " -T", filetype, " ", inn, " -o ", out)); + engine := "dot"; + if IsBound(opt.engine) then + engine := opt.engine; + if not engine in ["dot", "neato", "twopi", "circo", + "fdp", "sfdp", "patchwork"] then + ErrorNoReturn("the component \"engine\" of the 2nd argument ", + " must be one of: \"dot\", \"neato\", ", + "\"twopi\", \"circo\", \"fdp\", \"sfdp\", ", + "or \"patchwork\""); fi; - Exec(Concatenation(viewer, " ", out, " 2>/dev/null 1>/dev/null &")); - end); -fi; + fi; + + FileString(inn, str); + if type = "latex" then + Exec(Concatenation("cd ", dir, ";", + "pdflatex ", file, " 2>/dev/null 1>/dev/null")); + else + Exec(Concatenation(engine, " -T", filetype, " ", inn, " -o ", out)); + fi; + Exec(Concatenation(viewer, " ", out, " 2>/dev/null 1>/dev/null &")); +end); diff --git a/init.g b/init.g index 2d54325..42a7379 100644 --- a/init.g +++ b/init.g @@ -8,4 +8,7 @@ ############################################################################# ## +ReadPackage("graphviz", "gap/error.gd"); ReadPackage("graphviz", "gap/dot.gd"); +ReadPackage("graphviz", "gap/gv.gd"); +ReadPackage("graphviz", "gap/splash.gd"); diff --git a/read.g b/read.g index 4dbac1a..a13f5fb 100644 --- a/read.g +++ b/read.g @@ -7,6 +7,8 @@ ## ############################################################################# ## + ReadPackage("graphviz", "gap/dot.gi"); +ReadPackage("graphviz", "gap/error.gi"); +ReadPackage("graphviz", "gap/gv.gi"); ReadPackage("graphviz", "gap/splash.gi"); -ReadPackage("graphviz", "gap/defaults.gi"); diff --git a/tst/dot.tst b/tst/dot.tst index 4f0c708..f9c39e1 100644 --- a/tst/dot.tst +++ b/tst/dot.tst @@ -8,7 +8,7 @@ ############################################################################# ## -#@local a, b, color, e, g, label, n, shape +#@local a, b, color, e, g, gv, label, n, shape gap> START_TEST("graphviz package: dot.tst"); gap> LoadPackage("graphviz", false);; @@ -37,6 +37,9 @@ gap> n := GraphvizAddNode(g, "test");; gap> GraphvizSetAttrs(n, rec(color := "red", label := "lab"));; gap> AsString(g); "//dot\ngraph {\n\ttest [color=red, label=lab]\n}\n" +gap> GraphvizRemoveNode(g, "banana"); +Error, the 2nd argument (node name string) "banana" is not a node of the 1st a\ +rgument (a graphviz (di)graph/context) # Test stringify with edge (digraphs) gap> g := GraphvizDigraph();; @@ -72,7 +75,7 @@ gap> g := GraphvizGraph();; gap> n := GraphvizAddNode(g, "n");; gap> GraphvizSetAttr(n, "test", "false"); #I unknown attribute "test", the graphviz object may no longer be valid, it can be removed using GraphvizRemoveAttr - + # Test unknown attributes (graph) gap> g := GraphvizGraph();; @@ -104,5 +107,128 @@ gap> GraphvizSetAttr(e, "label", "before>>hello");; gap> AsString(g); "//dot\ngraph {\n\ta\n\tb\n\ta -- b [label=\"before>>hello\"]\n}\n" +# Test GraphvizSetNodeLabels +gap> gv := GraphvizGraph("xxx"); + +gap> GraphvizAddNode(gv, 1); + +gap> GraphvizAddNode(gv, 2); + +gap> GraphvizAddNode(gv, 3); + +gap> GraphvizSetNodeLabels(gv, ["i", "ii", "iii"]); + +gap> Print(AsString(gv)); +//dot +graph xxx { + 1 [label=i] + 2 [label=ii] + 3 [label=iii] +} +gap> GraphvizSetNodeLabels(gv, ["a", "b", "c"]); + +gap> Print(AsString(gv)); +//dot +graph xxx { + 1 [label=a] + 2 [label=b] + 3 [label=c] +} +gap> GraphvizSetNodeLabels(gv, ["i", "ii"]); +Error, the 2nd argument (list of node labels) has incorrect length, expected 3\ +, but found 2 +gap> GraphvizSetNodeLabels(gv, ["i", "ii", "iii", "iv"]); +Error, the 2nd argument (list of node labels) has incorrect length, expected 3\ +, but found 4 + +# Test GraphvizSetNodeColors +gap> gv := GraphvizGraph("xxx"); + +gap> GraphvizAddNode(gv, 1); + +gap> GraphvizAddNode(gv, 2); + +gap> GraphvizAddNode(gv, 3); + +#@if CompareVersionNumbers(GAPInfo.Version, "4.12") +gap> GraphvizSetNodeColors(gv, ["i", "ii", "iii"]); +Error, invalid color "i" (list (string)), valid colors are RGB values or names\ + from the GraphViz 2.44.1 X11 Color Scheme http://graphviz.org/doc/info/colors\ +.html +#@else +gap> GraphvizSetNodeColors(gv, ["i", "ii", "iii"]); +Error, invalid color "i" (list (string)), valid colors are RGB values or names\ + from the GraphViz 2.44.1 X11 Color Sch\ +eme http://graphviz.org/doc/info/colors.html +#@fi +gap> GraphvizSetNodeColors(gv, ["red", "green", "blue"]); + +gap> Print(AsString(gv)); +//dot +graph xxx { + 1 [color=red, style=filled] + 2 [color=green, style=filled] + 3 [color=blue, style=filled] +} +gap> GraphvizSetNodeColors(gv, ["red", "#00FF00", "blue"]); + +gap> Print(AsString(gv)); +//dot +graph xxx { + 1 [color=red, style=filled] + 2 [color="#00FF00", style=filled] + 3 [color=blue, style=filled] +} +gap> GraphvizSetNodeColors(gv, ["#FF0000", "#00FF00", "#0000FF"]); + +gap> Print(AsString(gv)); +//dot +graph xxx { + 1 [color="#FF0000", style=filled] + 2 [color="#00FF00", style=filled] + 3 [color="#0000FF", style=filled] +} +#@if CompareVersionNumbers(GAPInfo.Version, "4.12") +gap> GraphvizSetNodeColors(gv, ["#FF0000", "#00FF00", "#0000FG"]); +Error, invalid color "#0000FG" (list (string)), valid colors are RGB values or\ + names from the GraphViz 2.44.1 X11 Color Scheme http://graphviz.org/doc/info/\ +colors.html +#@else +gap> GraphvizSetNodeColors(gv, ["#FF0000", "#00FF00", "#0000FG"]); +Error, invalid color "#0000FG" (list (string)), valid colors are RGB values or\ + names from the GraphViz 2.44.1 X11 Color Sch\ +eme http://graphviz.org/doc/info/colors.html +#@fi +gap> GraphvizSetNodeColors(gv, ["#FF0000", "#00FF00"]); +Error, the number of node colors must be the same as the number of nodes, expe\ +cted 3 but found 2 +gap> GraphvizAddEdge(gv, "a", "b"); + +gap> GraphvizNodes(gv); +rec( 1 := , 2 := , + 3 := , a := , + b := ) + +# Test attribute names with spaces (TODO are there any valid such??) +gap> gv := GraphvizGraph("xxx"); + +gap> n := GraphvizAddNode(gv, 1); + +gap> n := GraphvizSetAttr(n, "probably not ok", 1); +#I unknown attribute "probably not ok", the graphviz object may no longer be valid, it can be removed using GraphvizRemoveAttr + +gap> Print(AsString(gv)); +//dot +graph xxx { + 1 ["probably not ok"=1] +} +gap> GraphvizSetAttr(n, "label", "<<>>"); + +gap> Print(AsString(gv)); +//dot +graph xxx { + 1 [label=<<>>, "probably not ok"=1] +} + # gap> STOP_TEST("graphviz package: dot.tst", 0); diff --git a/tst/edge.tst b/tst/edge.tst index 5f5f8d4..6381a04 100644 --- a/tst/edge.tst +++ b/tst/edge.tst @@ -24,9 +24,9 @@ gap> g := GraphvizGraph();; gap> e := GraphvizAddEdge(g, "a", "b"); gap> GraphvizHead(e); - + gap> GraphvizTail(e); - + # Test filtering edges by names (digraph) gap> g := GraphvizDigraph();; @@ -36,19 +36,19 @@ gap> c := GraphvizAddNode(g, "c");; gap> d := GraphvizAddNode(g, "d");; gap> ab := GraphvizAddEdge(g, a, b);; gap> cd := GraphvizAddEdge(g, c, d);; -gap> GraphvizFilterEnds(g, "a", "c"); +gap> GraphvizRemoveEdges(g, "a", "c"); -gap> GraphvizFilterEnds(g, "b", "d"); +gap> GraphvizRemoveEdges(g, "b", "d"); -gap> GraphvizFilterEnds(g, "a", "b"); +gap> GraphvizRemoveEdges(g, "a", "b"); gap> GraphvizEdges(g); [ ] -gap> GraphvizFilterEnds(g, "c", "d"); +gap> GraphvizRemoveEdges(g, "c", "d"); gap> GraphvizEdges(g); [ ] -gap> GraphvizFilterEnds(g, "c", "d"); +gap> GraphvizRemoveEdges(g, "c", "d"); # Test filtering edges by names (graph) @@ -59,22 +59,23 @@ gap> c := GraphvizAddNode(g, "c");; gap> d := GraphvizAddNode(g, "d");; gap> ab := GraphvizAddEdge(g, a, b);; gap> cd := GraphvizAddEdge(g, c, d);; -gap> GraphvizFilterEnds(g, "a", "c"); +gap> GraphvizRemoveEdges(g, "a", "c"); -gap> GraphvizFilterEnds(g, "b", "d"); +gap> GraphvizRemoveEdges(g, "b", "d"); -gap> GraphvizFilterEnds(g, "b", "a"); +gap> GraphvizRemoveEdges(g, "b", "a"); gap> GraphvizEdges(g); [ ] -gap> GraphvizFilterEnds(g, "d", "c"); +gap> GraphvizRemoveEdges(g, "d", "c"); gap> GraphvizEdges(g); [ ] -gap> GraphvizFilterEnds(g, "c", "d"); +gap> GraphvizRemoveEdges(g, "c", "d"); -# Test adding edge between nodes which are not in the graph, but there exists nodes in the graph which share their names. +# Test adding edge between nodes which are not in the graph, but there exists +# nodes in the graph which share their names. gap> g := GraphvizGraph();; gap> g1 := GraphvizGraph();; gap> a1 := GraphvizAddNode(g, "a");; @@ -82,10 +83,20 @@ gap> d := GraphvizAddNode(g, "d");; gap> a2 := GraphvizAddNode(g1, "a");; gap> c := GraphvizAddNode(g1, "c");; gap> e1 := GraphvizAddEdge(g, d, a1);; +#@if CompareVersionNumbers(GAPInfo.Version, "4.12") gap> e2 := GraphvizAddEdge(g, a2, c);; -Error, Different node in graph with name a +Error, The 2nd argument (edge) has head node named "a" but there is already a \ +node with this name in the 1st argument (a graphviz (di)graph / context) named\ + "" +#@fi gap> GraphvizEdges(g); [ ] +#@if CompareVersionNumbers(GAPInfo.Version, "4.12") +gap> e2 := GraphvizAddEdge(g, c, a2);; +Error, The 2nd argument (edge) has tail node named "c" but there is already a \ +node with this name in the 1st argument (a graphviz (di)graph / context) named\ + "" +#@fi # Test adding an edge reuses a node automatically gap> g := GraphvizGraph();; @@ -110,13 +121,13 @@ gap> GraphvizAddEdge(g, 1, "a"); gap> GraphvizAddEdge(g, 1, 1); -gap> GraphvizFilterEnds(g, "a", 1);; +gap> GraphvizRemoveEdges(g, "a", 1);; gap> GraphvizEdges(g); [ , ] -gap> GraphvizFilterEnds(g, 1, "a");; +gap> GraphvizRemoveEdges(g, 1, "a");; gap> GraphvizEdges(g); [ ] -gap> GraphvizFilterEnds(g, 1, 1);; +gap> GraphvizRemoveEdges(g, 1, 1);; gap> GraphvizEdges(g); [ ] @@ -132,6 +143,8 @@ rec( color := "red", label := "1" ) gap> n["color"] := "blue";; gap> GraphvizAttrs(n); rec( color := "blue", label := "1" ) +gap> n[1]; +fail # Test getting attributes using the [] syntax gap> g := GraphvizGraph();; @@ -143,16 +156,16 @@ gap> n["color"]; # Test set label (edge) gap> g := GraphvizGraph();; gap> n := GraphvizAddEdge(g, "n", "m");; -gap> GraphvizSetLabel(n, "test");; +gap> GraphvizSetAttr(n, "label", "test");; gap> GraphvizAttrs(n); rec( label := "test" ) # Test set color (edge) gap> g := GraphvizGraph();; gap> n := GraphvizAddEdge(g, "n", "m");; -gap> GraphvizSetColor(n, "red");; +gap> GraphvizSetAttr(n, "color", "red");; gap> GraphvizAttrs(n); rec( color := "red" ) # -gap> STOP_TEST("graphviz package: edge.tst", 0); +gap> STOP_TEST("graphviz package: edge.tst"); diff --git a/tst/examples/angles.tst b/tst/examples/angles.tst index fc39502..ee25a19 100644 --- a/tst/examples/angles.tst +++ b/tst/examples/angles.tst @@ -16,19 +16,19 @@ gap> START_TEST("graphviz package: examples/angles.tst"); gap> LoadPackage("graphviz"); true gap> g := GraphvizDigraph("G"); - + gap> GraphvizSetAttr(g, "bgcolor", "blue"); - + gap> cluster1 := GraphvizAddSubgraph(g, "cluster_1"); - + gap> GraphvizSetAttr(cluster1, "fontcolor", "white"); - + gap> GraphvizSetAttr(cluster1, Concatenation("node[shape=circle, style=filled,", > "fillcolor=\"white:black\", gradientangle=360, label=\"n9:n360\",", > "fontcolor=black]")); - + gap> GraphvizAddNode(cluster1, "n9"); - + gap> pairs := ListN([8, 7 .. 1], [315, 270 .. 0], {x, y} -> [x, y]); [ [ 8, 315 ], [ 7, 270 ], [ 6, 225 ], [ 5, 180 ], [ 4, 135 ], [ 3, 90 ], [ 2, 45 ], [ 1, 0 ] ] @@ -41,17 +41,17 @@ gap> for pair in pairs do gap> GraphvizSetAttr(cluster1, > "label", > "Linear Angle Variations (white to black gradient)"); - + gap> cluster2 := GraphvizAddSubgraph(g, "cluster_2"); - + gap> GraphvizSetAttr(cluster2, "fontcolor", "white"); - + gap> GraphvizSetAttr(cluster2, Concatenation("node[shape=circle, style=radial,", > "fillcolor=\"white:black\", gradientangle=360,", > "label=\"n9:n360\", fontcolor=black]")); - + gap> GraphvizAddNode(cluster2, "n18"); - + gap> pairs := ListN([17, 16 .. 10], [315, 270 .. 0], {x, y} -> [x, y]); [ [ 17, 315 ], [ 16, 270 ], [ 15, 225 ], [ 14, 180 ], [ 13, 135 ], [ 12, 90 ], [ 11, 45 ], [ 10, 0 ] ] @@ -63,7 +63,7 @@ gap> for pair in pairs do > od; gap> GraphvizSetAttr(cluster2, "label", > "Radial Angle Variations (white to black gradient)"); - + gap> GraphvizAddEdge(g, "n5", "n14"); diff --git a/tst/examples/btree.tst b/tst/examples/btree.tst index 555996e..d693e1b 100644 --- a/tst/examples/btree.tst +++ b/tst/examples/btree.tst @@ -18,27 +18,27 @@ true # gap> s := GraphvizDigraph("g"); - + gap> GraphvizSetAttr(s, "node [shape=record, height=.1]"); - + gap> GraphvizSetAttr(GraphvizAddNode(s, "node0"), "label", " | G|"); - + gap> GraphvizSetAttr(GraphvizAddNode(s, "node1"), "label", " | E|"); - + gap> GraphvizSetAttr(GraphvizAddNode(s, "node2"), "label", " | B|"); - + gap> GraphvizSetAttr(GraphvizAddNode(s, "node3"), "label", " | F|"); - + gap> GraphvizSetAttr(GraphvizAddNode(s, "node4"), "label", " | R|"); - + gap> GraphvizSetAttr(GraphvizAddNode(s, "node5"), "label", " | H|"); - + gap> GraphvizSetAttr(GraphvizAddNode(s, "node6"), "label", " | Y|"); - + gap> GraphvizSetAttr(GraphvizAddNode(s, "node7"), "label", " | A|"); - + gap> GraphvizSetAttr(GraphvizAddNode(s, "node8"), "label", " | C|"); - + gap> GraphvizAddEdge(s, "node0:f2", "node4:f1"); gap> GraphvizAddEdge(s, "node0:f0", "node1:f1"); diff --git a/tst/examples/cluster.tst b/tst/examples/cluster.tst index 2786feb..32023fa 100644 --- a/tst/examples/cluster.tst +++ b/tst/examples/cluster.tst @@ -15,17 +15,17 @@ gap> START_TEST("graphviz package: examples/cluster.tst"); gap> LoadPackage("graphviz"); true gap> graph := GraphvizDigraph("G"); - + # gap> cluster0 := GraphvizAddSubgraph(graph, "cluster_0"); - + gap> GraphvizSetAttr(cluster0, "color=\"lightgrey\""); - + gap> GraphvizSetAttr(cluster0, "style=\"filled\""); - + gap> GraphvizSetAttr(cluster0, "node [color=\"white\", style=\"filled\"]"); - + gap> GraphvizAddEdge(cluster0, "a0", "a1"); gap> GraphvizAddEdge(cluster0, "a1", "a2"); @@ -33,15 +33,15 @@ gap> GraphvizAddEdge(cluster0, "a1", "a2"); gap> GraphvizAddEdge(cluster0, "a2", "a3"); gap> GraphvizSetAttr(cluster0, "label=\"process #1\""); - + # gap> cluster1 := GraphvizAddSubgraph(graph, "cluster_1"); - + gap> GraphvizSetAttr(cluster1, "color=\"blue\""); - + gap> GraphvizSetAttr(cluster1, "node [style=\"filled\"]"); - + gap> GraphvizAddEdge(cluster1, "b0", "b1"); gap> GraphvizAddEdge(cluster1, "b1", "b2"); @@ -49,7 +49,7 @@ gap> GraphvizAddEdge(cluster1, "b1", "b2"); gap> GraphvizAddEdge(cluster1, "b2", "b3"); gap> GraphvizSetAttr(cluster1, "label=\"process #2\""); - + # gap> GraphvizAddEdge(graph, "start", "a0"); @@ -69,9 +69,9 @@ gap> GraphvizAddEdge(graph, "b3", "end"); # gap> GraphvizSetAttr(graph["start"], "shape", "Mdiamond"); - + gap> GraphvizSetAttr(graph["end"], "shape", "Msquare"); - + # gap> AsString(graph); @@ -84,4 +84,4 @@ t -> a0\n\tstart -> b0\n\ta1 -> b3\n\tb2 -> a3\n\ta3 -> a0\n\tend [shape=Msqua\ re]\n\ta3 -> end\n\tb3 -> end\n}\n" # -gap> STOP_TEST("graphviz package: cluster.tst"); +gap> STOP_TEST("graphviz package: examples/cluster.tst"); diff --git a/tst/examples/cluster_edge.tst b/tst/examples/cluster_edge.tst index 3a017fe..4b4a9f2 100644 --- a/tst/examples/cluster_edge.tst +++ b/tst/examples/cluster_edge.tst @@ -18,11 +18,11 @@ true # gap> g := GraphvizDigraph("G"); - + gap> GraphvizSetAttr(g, "compound=true"); - + gap> cluster0 := GraphvizAddSubgraph(g, "cluster0"); - + gap> GraphvizAddEdge(cluster0, "a", "b"); gap> GraphvizAddEdge(cluster0, "a", "c"); @@ -32,7 +32,7 @@ gap> GraphvizAddEdge(cluster0, "b", "d"); gap> GraphvizAddEdge(cluster0, "c", "d"); gap> cluster1 := GraphvizAddSubgraph(g, "cluster1"); - + gap> GraphvizAddEdge(cluster1, "e", "g"); gap> GraphvizAddEdge(cluster1, "e", "f"); @@ -60,4 +60,4 @@ e -> g\n\tf\n\te -> f\n}\n\tb -> f [lhead=cluster1]\n\td -> e\n\tc -> g [lhead\ =cluster1, ltail=cluster0]\n\tc -> e [ltail=cluster0]\n\th\n\td -> h\n}\n" # -gap> STOP_TEST("graphviz package: cluster_edge.tst"); +gap> STOP_TEST("graphviz package: examples/cluster_edge.tst"); diff --git a/tst/examples/colors.tst b/tst/examples/colors.tst index d1f4d8c..1c044da 100644 --- a/tst/examples/colors.tst +++ b/tst/examples/colors.tst @@ -23,35 +23,35 @@ gap> g := GraphvizGraph(); # gap> node := GraphvizAddNode(g, "RGB: #40e0d0"); - + gap> GraphvizSetAttr(node, "style", "filled"); - + gap> GraphvizSetAttr(node, "fillcolor", "\"#40e0d0\""); - + # gap> node := GraphvizAddNode(g, "RGBA: #ff000042"); - + gap> GraphvizSetAttr(node, "style", "filled"); - + gap> GraphvizSetAttr(node, "fillcolor", "\"#ff000042\""); - + # gap> node := GraphvizAddNode(g, "HSV: 0.051 0.718 0.627"); - + gap> GraphvizSetAttr(node, "style", "filled"); - + gap> GraphvizSetAttr(node, "fillcolor", "0.051 0.718 0.627"); - + # gap> node := GraphvizAddNode(g, "name: deeppink"); - + gap> GraphvizSetAttr(node, "style", "filled"); - + gap> GraphvizSetAttr(node, "fillcolor", "deeppink"); - + # gap> AsString(g); @@ -65,4 +65,4 @@ gap> AsString(g); deeppink\" [fillcolor=deeppink, style=filled]\n}\n" # -gap> STOP_TEST("graphviz package: colors.tst"); +gap> STOP_TEST("graphviz package: examples/colors.tst"); diff --git a/tst/examples/er.tst b/tst/examples/er.tst index a1dcbf3..d48d7c0 100644 --- a/tst/examples/er.tst +++ b/tst/examples/er.tst @@ -18,52 +18,52 @@ true # gap> e := GraphvizGraph("ER"); - + gap> GraphvizSetAttr(e, "engine=\"neato\""); - + # gap> start := GraphvizAddContext(e, "context_start"); - + gap> GraphvizSetAttr(start, "node[shape=\"box\"]"); - + gap> GraphvizAddNode(start, "course"); - + gap> GraphvizAddNode(start, "institute"); - + gap> GraphvizAddNode(start, "student"); - + # gap> context1 := GraphvizAddContext(e, "context1"); - + gap> GraphvizSetAttr(context1, "node [shape=\"ellipse\"]"); - + gap> GraphvizSetAttr(GraphvizAddNode(context1, "name0"), "label", "name"); - + gap> GraphvizSetAttr(GraphvizAddNode(context1, "name1"), "label", "name"); - + gap> GraphvizSetAttr(GraphvizAddNode(context1, "name2"), "label", "name"); - + gap> GraphvizAddNode(context1, "code"); - + gap> GraphvizAddNode(context1, "grade"); - + gap> GraphvizAddNode(context1, "number"); - + # gap> context2 := GraphvizAddContext(e, "context2"); - + gap> GraphvizSetAttr(context2, > "node [shape=\"diamond\", style=\"filled\", color=\"lightgrey\"]"); - + gap> GraphvizAddNode(context2, "C-I"); - + gap> GraphvizAddNode(context2, "S-C"); - + gap> GraphvizAddNode(context2, "S-I"); - + # gap> GraphvizAddEdge(e, "name0", "course"); @@ -99,9 +99,9 @@ gap> GraphvizSetAttrs(GraphvizAddEdge(e, "course", "S-C"), # gap> GraphvizSetAttr(e, "label=\"Entity Relation Diagram\ndrawn by NEATO\""); - + gap> GraphvizSetAttr(e, "fontsize=\"20\""); - + # gap> AsString(e); @@ -131,4 +131,4 @@ t\n\t\"S-C\" -- student [label=m, len=1.00]\n\tcourse -- \"S-C\" [label=n, len\ =1.00]\n}\n" # -gap> STOP_TEST("graphviz package: er.tst"); +gap> STOP_TEST("graphviz package: examples/er.tst"); diff --git a/tst/examples/fsm.tst b/tst/examples/fsm.tst index ad70da3..9310d3c 100644 --- a/tst/examples/fsm.tst +++ b/tst/examples/fsm.tst @@ -18,31 +18,31 @@ true # gap> f := GraphvizDigraph("finite_state_machine"); - + gap> GraphvizSetAttr(f, "rankdir=LR"); - + gap> GraphvizSetAttr(f, "size=\"8,5\""); - + # gap> terminals := GraphvizAddContext(f, "terminals"); - + gap> GraphvizSetAttr(terminals, "node [shape=doublecircle]"); - + gap> GraphvizAddNode(terminals, "LR_0"); - + gap> GraphvizAddNode(terminals, "LR_3"); - + gap> GraphvizAddNode(terminals, "LR_4"); - + gap> GraphvizAddNode(terminals, "LR_8"); - + # gap> nodes := GraphvizAddContext(f, "nodes"); - + gap> GraphvizSetAttr(nodes, "node [shape=circle]"); - + gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_0", "LR_2"), "label", "\"SS(B)\""); gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_0", "LR_1"), "label", "\"SS(S)\""); @@ -86,4 +86,4 @@ LR_5\n\tLR_2 -> LR_5 [label=\"SS(a)\"]\n\tLR_2 -> LR_4 [label=\"S(A)\"]\n\tLR_\ \tLR_8 -> LR_5 [label=\"S(a)\"]\n\trankdir=LR size=\"8,5\" \n\n}\n" # -gap> STOP_TEST("graphviz package: fsm.tst"); +gap> STOP_TEST("graphviz package: examples/fsm.tst"); diff --git a/tst/examples/g_c_n.tst b/tst/examples/g_c_n.tst index f38f5b7..560cb09 100644 --- a/tst/examples/g_c_n.tst +++ b/tst/examples/g_c_n.tst @@ -18,22 +18,22 @@ true # gap> g := GraphvizGraph("G"); - + gap> GraphvizSetAttr(g, > "bgcolor=\"purple:pink\" label=\"agraph\" fontcolor=\"white\""); - + gap> cluster1 := GraphvizAddSubgraph(g, "cluster1"); - + gap> GraphvizSetAttr(cluster1, Concatenation( > "fillcolor=\"blue:cyan\" label=\"acluster\" fontcolor=\"white\"", > "style=\"filled\" gradientangle=270\n")); - + gap> GraphvizSetAttr(cluster1, Concatenation( > "node [shape=box, fillcolor=\"red:yellow\",", > " style=\"filled\", gradientangle=90]")); - + gap> GraphvizAddNode(cluster1, "anode"); - + #@if CompareVersionNumbers(GAPInfo.Version, "4.12.0") gap> Print(AsString(g)); @@ -63,4 +63,4 @@ tangle=270 #@fi # -gap> STOP_TEST("graphviz package: g_c_n.tst"); +gap> STOP_TEST("graphviz package: examples/g_c_n.tst"); diff --git a/tst/examples/hello.tst b/tst/examples/hello.tst index d9af8ae..a7f8f38 100644 --- a/tst/examples/hello.tst +++ b/tst/examples/hello.tst @@ -17,11 +17,11 @@ true # gap> graph := GraphvizDigraph("G"); - + gap> GraphvizAddEdge(graph, "hello", "world"); gap> AsString(graph); "//dot\ndigraph G {\n\thello\n\tworld\n\thello -> world\n}\n" # -gap> STOP_TEST("graphviz package: hello.tst"); +gap> STOP_TEST("graphviz package: examples/hello.tst"); diff --git a/tst/examples/process.tst b/tst/examples/process.tst index 9b922b8..4a57db2 100644 --- a/tst/examples/process.tst +++ b/tst/examples/process.tst @@ -18,9 +18,9 @@ true # gap> graph := GraphvizGraph("G"); - + gap> GraphvizSetAttr(graph, "engine=\"sfdp\""); - + # gap> GraphvizAddEdge(graph, "run", "intr"); @@ -55,4 +55,4 @@ gap> AsString(graph); wap -- runmem\n\tnew -- runmem\n}\n" # -gap> STOP_TEST("graphviz package: process.tst"); +gap> STOP_TEST("graphviz package: examples/process.tst"); diff --git a/tst/examples/rank_same.tst b/tst/examples/rank_same.tst index 685dee2..1e9a7f4 100644 --- a/tst/examples/rank_same.tst +++ b/tst/examples/rank_same.tst @@ -23,29 +23,29 @@ gap> g := GraphvizDigraph(); # gap> s1 := GraphvizAddSubgraph(g); - + gap> GraphvizSetAttr(s1, "rank=same"); - + gap> GraphvizAddNode(s1, "A"); - + gap> GraphvizAddNode(s1, "X"); - + # gap> GraphvizAddNode(g, "C"); - + # gap> s2 := GraphvizAddSubgraph(g); - + gap> GraphvizSetAttr(s2, "rank=same"); - + gap> GraphvizAddNode(s2, "B"); - + gap> GraphvizAddNode(s2, "D"); - + gap> GraphvizAddNode(s2, "Y"); - + # gap> GraphvizAddEdge(g, "A", "B"); @@ -64,4 +64,4 @@ raph no_name_3 {\n\trank=same \n\tB\n\tD\n\tY\n}\n\tA -> B\n\tA -> C\n\tC -> D\ \n\tX -> Y\n}\n" # -gap> STOP_TEST("graphviz package: rank_same.tst"); +gap> STOP_TEST("graphviz package: examples/rank_same.tst"); diff --git a/tst/examples/structs.tst b/tst/examples/structs.tst index 2068f9f..8de2523 100644 --- a/tst/examples/structs.tst +++ b/tst/examples/structs.tst @@ -16,9 +16,9 @@ gap> START_TEST("graphviz package: examples/structs.tst"); gap> LoadPackage("graphviz"); true gap> s := GraphvizDigraph("structs"); - + gap> GraphvizSetAttr(s, "node [shape=\"plaintext\"]"); - + # gap> GraphvizSetAttr(GraphvizAddNode(s, "struct1"), "label", @@ -28,7 +28,7 @@ gap> GraphvizSetAttr(GraphvizAddNode(s, "struct1"), "label", > middleright > > >"""); - + gap> GraphvizSetAttr(GraphvizAddNode(s, "struct2"), "label", > """< > @@ -36,7 +36,7 @@ gap> GraphvizSetAttr(GraphvizAddNode(s, "struct2"), "label", > > >
two
>"""); - + gap> GraphvizSetAttr(GraphvizAddNode(s, "struct3"), "label", > """< > @@ -54,7 +54,7 @@ gap> GraphvizSetAttr(GraphvizAddNode(s, "struct3"), "label", > > >
f
>"""); - + # gap> GraphvizAddEdge(s, "struct1:f1", "struct2:f0"); @@ -77,4 +77,4 @@ re\">d\ne\n\n\nf\n\n struct3:here\n}\n" # -gap> STOP_TEST("graphviz package: structs.tst"); +gap> STOP_TEST("graphviz package: examples/structs.tst"); diff --git a/tst/examples/traffic_lights.tst b/tst/examples/traffic_lights.tst index 295ac0f..3b6259c 100644 --- a/tst/examples/traffic_lights.tst +++ b/tst/examples/traffic_lights.tst @@ -16,15 +16,15 @@ gap> START_TEST("graphviz package: examples/traffic_lights.tst"); gap> LoadPackage("graphviz"); true gap> t := GraphvizDigraph("TrafficLights"); - + gap> GraphvizSetAttr(t, "engine=neato"); - + # gap> ctx1 := GraphvizAddSubgraph(t, "ctx1"); - + gap> GraphvizSetAttr(ctx1, "node [shape=\"box\"]"); - + gap> for i in [2, 1] do > GraphvizAddNode(ctx1, StringFormatted("gy{}", i)); > GraphvizAddNode(ctx1, StringFormatted("yr{}", i)); @@ -33,9 +33,9 @@ gap> for i in [2, 1] do # gap> ctx2 := GraphvizAddSubgraph(t, "ctx2"); - + gap> GraphvizSetAttr(ctx2, "node [shape=\"circle\", fixedsize=true, width=0.9]"); - + gap> for i in [2, 1] do > GraphvizAddNode(ctx2, StringFormatted("green{}", i)); > GraphvizAddNode(ctx2, StringFormatted("yellow{}", i)); @@ -69,14 +69,14 @@ gap> for pair in [[2, 1], [1, 2]] do # gap> GraphvizSetAttr(t, "overlap=\"false\""); - + gap> GraphvizSetAttr(t, > """label="PetriNet Model TrafficLights > Extracted from ConceptBase and laid out by Graphviz" > """); - + gap> GraphvizSetAttr(t, "fontsize=12"); - + # gap> AsString(t); @@ -91,4 +91,4 @@ ze=true, width=0.9] \n\tgreen2\n\tyellow2\n\tred2\n\tsafe2\n\tgreen1\n\tyellow\ g1\n\tgreen1 -> gy1\n\tyellow1 -> yr1\n\tred1 -> rg1\n}\n" # -gap> STOP_TEST("graphviz package: traffic_lights.tst"); +gap> STOP_TEST("graphviz package: examples/traffic_lights.tst"); diff --git a/tst/examples/unix.tst b/tst/examples/unix.tst index 5207345..833ce3d 100644 --- a/tst/examples/unix.tst +++ b/tst/examples/unix.tst @@ -18,10 +18,10 @@ true # gap> u := GraphvizDigraph("unix"); - + gap> GraphvizSetAttr( > u, "node [color=\"lightblue2\", style=\"filled\", size=\"6,6\"]"); - + # gap> GraphvizAddEdge(u, "5th Edition", "6th Edition"); @@ -283,4 +283,4 @@ gap> AsString(u) = true # -gap> STOP_TEST("graphviz package: unix.tst"); +gap> STOP_TEST("graphviz package: examples/unix.tst"); diff --git a/tst/graph.tst b/tst/graph.tst index a0aea1d..1bc010e 100644 --- a/tst/graph.tst +++ b/tst/graph.tst @@ -18,44 +18,44 @@ gap> GraphvizGraph(); # Test graph constructor gap> GraphvizGraph("test-name"); - + # Test digraph printing gap> x := GraphvizDigraph("test-name"); - + gap> x := GraphvizDigraph(); # Test adding nodes gap> g := GraphvizGraph();; gap> n := GraphvizAddNode(g, "n"); - + gap> g; gap> GraphvizNodes(g); -rec( n := ) +rec( n := ) gap> GraphvizAddNode(g, "x"); - + gap> g; gap> GraphvizNodes(g); -rec( n := , x := ) +rec( n := , x := ) # Test add node (name) gap> g := GraphvizGraph();; gap> GraphvizAddNode(g, "n"); - + gap> GraphvizNodes(g); -rec( n := ) +rec( n := ) gap> GraphvizAddNode(g, "x"); - + gap> GraphvizNodes(g); -rec( n := , x := ) +rec( n := , x := ) # Test has nodes gap> g := GraphvizGraph();; gap> n := GraphvizAddNode(g, "n"); - + gap> GV_HasNode(g, "n"); true gap> GV_HasNode(g, "x"); @@ -92,6 +92,31 @@ gap> g; gap> GraphvizEdges(g); [ , ] +gap> GraphvizEdges(g, "a", "c"); +[ ] +gap> GraphvizEdges(g, "a", "b"); +[ ] +gap> GraphvizAddEdge(g, "a", "b"); + +gap> GraphvizEdges(g, "a", "b"); +[ , ] +#@if CompareVersionNumbers(GAPInfo.Version, "4.12") +gap> GraphvizEdges(g, "x", "b"); +Error, the 2nd argument "x" (head of an edge) is not a node of the 1st argumen\ +t (a graphviz graph or digraph) +gap> GraphvizEdges(g, "a", "y"); +Error, the 3rd argument "y" (tail of an edge) is not a node of the 1st argumen\ +t (a graphviz graph or digraph) +#@else +gap> GraphvizEdges(g, "x", "b"); +Error, the 2nd argument "x" (head of an edge) is not a node of the 1st argumen\ +t (a graphviz graph or dig\ +raph) +gap> GraphvizEdges(g, "a", "y"); +Error, the 3rd argument "y" (tail of an edge) is not a node of the 1st argumen\ +t (a graphviz graph or dig\ +raph) +#@fi # Test adding edge with different nodes with the same name gap> g := GraphvizGraph();; @@ -99,7 +124,8 @@ gap> GraphvizAddEdge(g, "a", "b");; gap> GraphvizAddEdge(g, "a", "c"); gap> GraphvizNodes(g); -rec( a := , b := , c := ) +rec( a := , b := , + c := ) gap> GraphvizAddEdge(g, "c", "a"); gap> GraphvizAddEdge(g, "b", "d"); @@ -126,13 +152,14 @@ gap> GraphvizAddEdge(g, c, d);; gap> GraphvizRemoveNode(g, a); gap> GraphvizNodes(g); -rec( b := , c := , d := ) +rec( b := , c := , + d := ) gap> GraphvizEdges(g); [ ] gap> GraphvizRemoveNode(g, b); gap> GraphvizNodes(g); -rec( c := , d := ) +rec( c := , d := ) # Test removing node gap> g := GraphvizGraph();; @@ -141,23 +168,26 @@ gap> GraphvizAddEdge(g, "c", "d");; gap> GraphvizRemoveNode(g, "a"); gap> GraphvizNodes(g); -rec( b := , c := , d := ) +rec( b := , c := , + d := ) gap> GraphvizEdges(g); [ ] gap> GraphvizRemoveNode(g, "b"); gap> GraphvizNodes(g); -rec( c := , d := ) +rec( c := , d := ) # Test renaming graph gap> g := GraphvizGraph();; gap> GraphvizSetName(g, "test"); - + +gap> GraphvizSetName(g, 1); + # Test global attributes graph gap> g := GraphvizGraph();; gap> GraphvizSetName(g, "test"); - + # Test global attributes graph gap> g := GraphvizGraph();; @@ -196,48 +226,39 @@ gap> g := GraphvizGraph();; gap> n1 := GraphvizAddNode(g, "test");; gap> n2 := GraphvizAddNode(g, "abc");; gap> g["test"]; - + gap> g["abc"]; - + # Test getting a node with a non-string name using bracket notation gap> g := GraphvizGraph();; gap> n1 := GraphvizAddNode(g, 1);; gap> n2 := GraphvizAddNode(g, ["a"]);; gap> g[1]; - + gap> g[["a"]]; - - -# Test making a graph with a non-string name -gap> g := GraphvizGraph(11); - - -# Test setting a graph name to a non-string value -gap> g := GraphvizGraph(11); - -gap> GraphvizSetName(g, ["a"]); - - -# Test making a digraph with a non-string name -gap> g := GraphvizDigraph(11); - - -# Test setting a digraph name to a non-string value -gap> g := GraphvizDigraph(11); - -gap> GraphvizSetName(g, ["a"]); - - -# Test set label (graph) + gap> g := GraphvizGraph();; -gap> GraphvizSetLabel(g, "test");; +gap> GraphvizSetAttr(g, "label", "test");; gap> GraphvizAttrs(g); [ "label=test" ] +gap> GraphvizSetAttr(g, 1, 2); +#I unknown attribute "1", the graphviz object may no longer be valid, it can be removed using GraphvizRemoveAttr + +gap> GraphvizAttrs(g); +[ "label=test", "1=2" ] +gap> GraphvizRemoveAttr(g, "1=2"); + +gap> GraphvizAttrs(g); +[ "label=test", "1=2" ] +gap> GraphvizRemoveAttr(g, "1"); + +gap> GraphvizAttrs(g); +[ "label=test", "1=2" ] # Test set color (graph) gap> g := GraphvizGraph();; -gap> GraphvizSetColor(g, "red");; +gap> GraphvizSetAttr(g, "color", "red");; gap> GraphvizAttrs(g); [ "color=red" ] diff --git a/tst/node.tst b/tst/node.tst index 6388f06..cbf9313 100644 --- a/tst/node.tst +++ b/tst/node.tst @@ -14,7 +14,7 @@ gap> LoadPackage("graphviz", false);; # Test node constructor gap> GraphvizAddNode(GraphvizGraph(), "test-node"); - + # Test renaming nodes fails gap> n := GraphvizAddNode(GraphvizGraph(), "a");; @@ -24,7 +24,7 @@ Error, no 1st choice method found for `GraphvizName' on 2 arguments # Test making a node with an all whitespace name gap> n := GraphvizAddNode(GraphvizGraph(), " "); - + # Test making a node with empty name fails gap> n := GraphvizAddNode(GraphvizGraph(), ""); @@ -32,7 +32,7 @@ Error, the 2nd argument (string/node name) cannot be empty # Test whitespace in node names gap> n := GraphvizAddNode(GraphvizGraph(), "a a "); - + # Test modifying attributes gap> n := GraphvizAddNode(GraphvizGraph(), "t");; @@ -59,10 +59,20 @@ gap> AsString(g); # Test non-string name containing ':' gap> g := GraphvizGraph();; -gap> GraphvizAddNode(g, 111); - +gap> n := GraphvizAddNode(g, 111); + gap> AsString(g); "//dot\ngraph {\n\t111\n}\n" +gap> n[1]; +fail +gap> n[1] := 2; +2 +gap> n[1]; +"2" +gap> GraphvizRemoveAttr(n, 1); + +gap> n[1]; +fail # Test removing a node with a non-string name gap> g := GraphvizGraph();; @@ -94,14 +104,14 @@ gap> n["color"]; # Test set label (node) gap> g := GraphvizGraph();; gap> n := GraphvizAddNode(g, "n");; -gap> GraphvizSetLabel(n, "test");; +gap> GraphvizSetAttr(n, "label", "test");; gap> GraphvizAttrs(n); rec( label := "test" ) # Test set color (node) gap> g := GraphvizGraph();; gap> n := GraphvizAddNode(g, "n");; -gap> GraphvizSetColor(n, "red");; +gap> GraphvizSetAttr(n, "color", "red");; gap> GraphvizAttrs(n); rec( color := "red" ) @@ -110,7 +120,8 @@ gap> g := GraphvizGraph();; gap> n := GraphvizAddNode(g, "n");; gap> s := GraphvizGraph();; gap> GraphvizAddNode(s, n); -Error, Cannot add node objects directly to graphs. Please use the node's name. +Error, it is not currently possible to add Graphviz node objects directly to G\ +raphviz graphs or digraphs, use the node's name instead # gap> STOP_TEST("graphviz package: node.tst", 0); diff --git a/tst/subgraph.tst b/tst/subgraph.tst index 94d0bc3..de7eede 100644 --- a/tst/subgraph.tst +++ b/tst/subgraph.tst @@ -8,59 +8,70 @@ ############################################################################# ## -#@local a, a1, a2, a3, b, b1, b2, b3, c, child, ctx, g, main, n, o, parent, s -#@local s1, s11, s2, sibling +#@local a, a1, a2, a3, b, b1, b2, b3, c, child, ctx, g, gv, legend, main, n, o +#@local parent, s, s1, s11, s2, sibling gap> START_TEST("graphviz package: subgraph.tst"); gap> LoadPackage("graphviz", false);; # Test creating subgraphs (named) gap> g := GraphvizGraph();; gap> GraphvizAddSubgraph(g, "test-graph"); - + gap> g := GraphvizDigraph();; gap> GraphvizAddSubgraph(g, "test-digraph"); - + gap> g := GraphvizGraph();; gap> GraphvizAddContext(g, "test-context"); - + # Test no-name constructors gap> g := GraphvizGraph();; gap> GraphvizAddSubgraph(g); - + gap> g := GraphvizDigraph();; gap> GraphvizAddSubgraph(g); - + gap> g := GraphvizGraph();; gap> GraphvizAddContext(g); - + +#@if CompareVersionNumbers(GAPInfo.Version, "4.12") +gap> GraphvizAddContext(g, "no_name_1"); +Error, the 1st argument (a graphviz (di)graph/context) already has a context o\ +r subgraph with name "no_name_1" +#@else +gap> GraphvizAddContext(g, "no_name_1"); +Error, the 1st argument (a graphviz (di)graph/context) already has a context o\ +r subgr\ +aph with name "no_name_1" +#@fi # Test no-name constructor graphs' names increment gap> g := GraphvizGraph();; gap> GraphvizAddSubgraph(g); - + gap> GraphvizAddSubgraph(g); - + gap> GraphvizAddSubgraph(g); - + gap> GraphvizAddContext(g); - + # Test no-name constructor graphs' names increment (contexts) gap> g := GraphvizGraph();; gap> GraphvizAddContext(g); - + gap> GraphvizAddContext(g); - + gap> GraphvizAddContext(g); - + # Test getting subgraphs gap> g := GraphvizGraph();; gap> GraphvizAddSubgraph(g, "a");; gap> GraphvizAddContext(g, "b");; gap> GraphvizSubgraphs(g); -rec( a := , b := ) +rec( a := , + b := ) # Test adding a node to a subgraph (does or does not add to parent???) # TODO need to nail down expected behaviour! @@ -70,29 +81,38 @@ gap> GraphvizAddNode(s, "n");; gap> GraphvizNodes(g); rec( ) gap> GraphvizNodes(s); -rec( n := ) +rec( n := ) # Test adding a node to a subgraph which is already in parent fails (by name) gap> g := GraphvizGraph("r");; gap> s := GraphvizAddSubgraph(g, "a");; gap> GraphvizAddNode(g, "n");; +#@if CompareVersionNumbers(GAPInfo.Version, "4.12") gap> GraphvizAddNode(s, "n"); -Error, Already node with name n in graph r. +Error, the 2nd argument (node) has name "n" but there is already a node with t\ +his name in the 1st argument (a graphviz (di)graph / context) named "a" +#@fi # Test adding a node to a graph which is already in child fails (by name) gap> g := GraphvizGraph();; gap> s := GraphvizAddSubgraph(g, "a");; gap> GraphvizAddNode(s, "n");; +#@if CompareVersionNumbers(GAPInfo.Version, "4.12") gap> GraphvizAddNode(g, "n"); -Error, Already node with name n in graph a. +Error, the 2nd argument (node) has name "n" but there is already a node with t\ +his name in the 1st argument (a graphviz (di)graph / context) named "" +#@fi # Test adding a node to a graph which is already in sibling fails (by name) gap> g := GraphvizGraph();; gap> s1 := GraphvizAddSubgraph(g, "a");; gap> s2 := GraphvizAddSubgraph(g, "b");; gap> GraphvizAddNode(s1, "n");; +#@if CompareVersionNumbers(GAPInfo.Version, "4.12") gap> GraphvizAddNode(s2, "n"); -Error, Already node with name n in graph a. +Error, the 2nd argument (node) has name "n" but there is already a node with t\ +his name in the 1st argument (a graphviz (di)graph / context) named "b" +#@fi # Test adding edges to subgraphs gap> g := GraphvizGraph();; @@ -117,7 +137,7 @@ gap> GraphvizAddEdge(parent, "x", "y");; gap> GraphvizAddEdge(main, "x", "y");; gap> GraphvizAddEdge(sibling, "x", "y");; gap> GraphvizAddEdge(child, "x", "y");; -gap> GraphvizFilterEnds(main, "x", "y");; +gap> GraphvizRemoveEdges(main, "x", "y");; gap> GraphvizEdges(g); [ ] gap> GraphvizEdges(parent); @@ -183,7 +203,7 @@ gap> s1 := GraphvizAddSubgraph(g);; gap> GraphvizAddNode(s1, "a");; gap> s2 := GraphvizAddSubgraph(g);; gap> GV_FindNode(s2, "a"); - + gap> GV_FindNode(s2, "b"); fail @@ -192,7 +212,7 @@ gap> g := GraphvizDigraph();; gap> s1 := GraphvizAddSubgraph(g);; gap> GraphvizAddNode(s1, "a");; gap> GV_FindNode(g, "a"); - + gap> GV_FindNode(g, "b"); fail @@ -201,7 +221,7 @@ gap> g := GraphvizDigraph();; gap> s1 := GraphvizAddSubgraph(g);; gap> GraphvizAddNode(g, "a");; gap> GV_FindNode(s1, "a"); - + gap> GV_FindNode(s1, "b"); fail @@ -212,7 +232,7 @@ gap> s2 := GraphvizAddSubgraph(g);; gap> s11 := GraphvizAddSubgraph(s1);; gap> GraphvizAddNode(s2, "a");; gap> GV_FindNode(s11, "a"); - + gap> GV_FindNode(s11, "b"); fail @@ -229,11 +249,11 @@ gap> GraphvizRemoveNode(g, "d");; gap> GraphvizNodes(g); rec( ) gap> GraphvizNodes(parent); -rec( a := ) +rec( a := ) gap> GraphvizNodes(sibling); -rec( b := ) +rec( b := ) gap> GraphvizNodes(child); -rec( c := ) +rec( c := ) # Test context attribute resetting gap> g := GraphvizDigraph();; @@ -252,39 +272,47 @@ edge [label=testing123] node[color=blue] edge[color=blue] \n\n}\n" # Test adding subgraphs with the same name gap> g := GraphvizDigraph();; gap> s1 := GraphvizAddSubgraph(g, "a");; +#@if CompareVersionNumbers(GAPInfo.Version, "4.12") gap> s2 := GraphvizAddSubgraph(g, "a"); -Error, The graph already contains a subgraph with name a. +Error, the 1st argument (a graphviz (di)graph/context) already has a subgraph \ +with name "a" +#@else +gap> s2 := GraphvizAddSubgraph(g, "a"); +Error, the 1st argument (a graphviz (di)graph/context) already has a subgraph \ +with na\ +me "a" +#@fi # Test getting subgraphs by name gap> g := GraphvizDigraph();; gap> s1 := GraphvizAddSubgraph(g, "a");; gap> s2 := GraphvizAddSubgraph(g, "b");; -gap> GraphvizGetSubgraph(g, "a"); - -gap> GraphvizGetSubgraph(g, "b"); - -gap> GraphvizGetSubgraph(g, "d"); +gap> GraphvizSubgraphs(g)["a"]; + +gap> GraphvizSubgraphs(g)["b"]; + +gap> GraphvizSubgraphs(g)["d"]; fail # Test getting context (subgraph) by name gap> g := GraphvizDigraph();; gap> s1 := GraphvizAddSubgraph(g, "a");; gap> s2 := GraphvizAddContext(g, "c");; -gap> GraphvizGetSubgraph(g, "a"); - -gap> GraphvizGetSubgraph(g, "c"); - +gap> GraphvizSubgraphs(g)["a"]; + +gap> GraphvizSubgraphs(g)["c"]; + # Test adding a nested subgraph gap> g := GraphvizGraph();; gap> s1 := GraphvizAddSubgraph(g, "a");; gap> s2 := GraphvizAddSubgraph(s1, "c");; -gap> GraphvizGetSubgraph(g, "a"); - -gap> GraphvizGetSubgraph(g, "c"); +gap> GraphvizSubgraphs(g)["a"]; + +gap> GraphvizSubgraphs(g)["c"]; fail -gap> GraphvizGetSubgraph(s1, "c"); - +gap> GraphvizSubgraphs(s1)["c"]; + # Test displaying a nested subgraph gap> g := GraphvizGraph();; @@ -296,32 +324,32 @@ gap> AsString(g); # Test subgraphs with non-string names gap> g := GraphvizGraph();; gap> GraphvizAddSubgraph(g, 11); - + # Test contexts with non-string names gap> g := GraphvizGraph();; gap> GraphvizAddContext(g, 11); - + # Test getting subgraphs with non-string names gap> g := GraphvizGraph();; gap> GraphvizAddContext(g, ["a"]);; -gap> GraphvizGetSubgraph(g, ["a"]); - +gap> GraphvizSubgraphs(g)[["a"]]; + # Test finding subgraph (parent) gap> g := GraphvizGraph("a");; gap> s := GraphvizAddSubgraph(g, "b");; -gap> o := GraphvizFindGraph(s, "a"); - +gap> o := GraphvizFindSubgraphRecursive(s, "a"); + gap> IsIdenticalObj(o, g); true # Test finding subgraph (child) gap> g := GraphvizGraph("a");; gap> s := GraphvizAddSubgraph(g, "b");; -gap> o := GraphvizFindGraph(g, "b"); - +gap> o := GraphvizFindSubgraphRecursive(g, "b"); + gap> IsIdenticalObj(o, s); true @@ -329,15 +357,15 @@ true gap> g := GraphvizGraph("a");; gap> s := GraphvizAddSubgraph(g, "b");; gap> s2 := GraphvizAddSubgraph(g, "c");; -gap> o := GraphvizFindGraph(s, "c"); - +gap> o := GraphvizFindSubgraphRecursive(s, "c"); + gap> IsIdenticalObj(o, s2); true # Test finding subgraph (self) gap> g := GraphvizGraph("a");; -gap> o := GraphvizFindGraph(g, "a"); - +gap> o := GraphvizFindSubgraphRecursive(g, "a"); + gap> IsIdenticalObj(o, g); true @@ -349,8 +377,8 @@ gap> a3 := GraphvizAddSubgraph(a2, "a3");; gap> b1 := GraphvizAddSubgraph(g, "b1");; gap> b2 := GraphvizAddSubgraph(b1, "b2");; gap> b3 := GraphvizAddSubgraph(b2, "b3");; -gap> o := GraphvizFindGraph(a3, "b3"); - +gap> o := GraphvizFindSubgraphRecursive(a3, "b3"); + gap> IsIdenticalObj(o, b3); true @@ -375,10 +403,43 @@ gap> AsString(g); # Test finding subgraph (non-string name) gap> g := GraphvizGraph("r");; gap> s := GraphvizAddSubgraph(g, 1);; -gap> o := GraphvizFindGraph(g, 1); - +gap> o := GraphvizFindSubgraphRecursive(g, 1); + gap> IsIdenticalObj(o, s); true +# Test a context containing a subgraph +gap> gv := GraphvizGraph("context+subgraph");; +gap> GraphvizSetAttr(gv, "node [shape=\"box\"]"); + +gap> legend := GraphvizAddContext(gv, "legend"); + +gap> GraphvizSetAttr(legend, "node [shape=plaintext]"); + +gap> GraphvizAddSubgraph(legend, "legend"); + +gap> Print(AsString(gv)); +//dot +graph context+subgraph { + node [shape="box"] +// legend context + node [shape=plaintext] +subgraph legend { +} + node [shape="box"] + +} + +# Test a context containing a subdigraph +gap> gv := GraphvizDigraph("context+subgraph");; +gap> GraphvizSetAttr(gv, "node [shape=\"box\"]"); + +gap> legend := GraphvizAddContext(gv, "legend"); + +gap> GraphvizSetAttr(legend, "node [shape=plaintext]"); + +gap> GraphvizAddSubgraph(legend, "legend"); + + # gap> STOP_TEST("graphviz package: subgraph.tst", 0);