Skip to content

Instantly share code, notes, and snippets.

@steveroush
Last active June 1, 2024 00:44
Show Gist options
  • Save steveroush/b020b29785704dbf37f79fbf02b3271f to your computer and use it in GitHub Desktop.
Save steveroush/b020b29785704dbf37f79fbf02b3271f to your computer and use it in GitHub Desktop.
gvlint - a "lint" program for Graphviz files
/********************************************************************
gvlint.gvpr - lint program for Graphviz input
USAGE:
gvpr -f gvlint.gvpr # just produces a "report" (assumes dot)
gvpr -cf gvlint.gvpr # report & reformatted input
gvpr -a neato -f gvlint.gvpr # report assuming neato attributes
gvpr -a S -f gvlint.gvpr myfile.gv # skip "pos not defined ..."
gvpr -a "neato A" -f gvlint.gvpr # report assuming neato attributes
# and show all attributes used
There is more that this program might do:
- test values against min & max
- test values against reasonable
- report usage of "legal" attributes that may not work due to known bugs
- e.g. "ortho & ports", ordering=, splines=curved & ???,
margin for cluster fails if 2 values, ...
note: see the source
git clone https://gitlab.com/graphviz/graphviz.gitlab.io
graphviz.gitlab.io/content/en/docs/attrs/*.md
********************************************************************/
BEGIN{
graph_t Root;
int i, cnt, messageCnt, reportAttributes, tooMany, skipPos;
int Ci, Si, Gi, Ni, Ei;
int errCount[], usedCount[];
string tok[int], usedAtts[], gType[];
string thisAttr, text1, text2, text3;
string msgStr, theEngine, dfltEngine, key;
string Double="?([+-])@(+([0-9])?(.*([0-9]))|\.+([0-9]))";
string Point=Double + "*([\t ]),*([\t ])" + Double;
string PointBang=Point + "?(!)";
string Rect=Point + "*([\t ]),*([\t ])" + Point;
string caret="^", bar="|";
string Engines[], Objects[], Types[], Minimums[];
obj_t objC[], objS[], objG[], objN[], objE[];
string help;
help=' USAGE:
gvpr -f gvlint.gvpr myfile.gv # just produces a "report" (assumes dot)
gvpr -cf gvlint.gvpr myfile.gv # report & reformatted input
gvpr -a neato -f gvlint.gvpr myfile.gv # report assuming neato attributes
gvpr -a S -f gvlint.gvpr myfile.gv # skip "pos not defined ..."
gvpr -a "neato A" -f gvlint.gvpr myfile.gv # report assuming neato attributes
# and show all attributes used';
/******** generated ************/
string lintVal[];
int v;
lintVal["Damping"] = "neato^G^double^0.0^";
lintVal["K"] = "fdp|sfdp^G|C^double^0^";
lintVal["TBbalance"] = "dot^G^string^^";
lintVal["URL"] = "ALL^E|N|G|C^escString^^";
lintVal["Attributes"] = "ALL^E|N|G|C^^^";
lintVal["area"] = "patchwork^N|C^double^>0^";
lintVal["arrowhead"] = "ALL^E^arrowType^^";
lintVal["arrowsize"] = "ALL^E^double^0.0^";
lintVal["arrowtail"] = "ALL^E^arrowType^^";
lintVal["_background"] = "ALL^G^xdot^^";
lintVal["bb"] = "ALL^C|G^rect^^";
lintVal["beautify"] = "sfdp^G^bool^^";
lintVal["bgcolor"] = "ALL^G|C^color|colorList^^";
lintVal["center"] = "ALL^G^bool^^";
lintVal["charset"] = "ALL^G^string^^";
lintVal["class"] = "ALL^E|N|C|G^string^^";
lintVal["cluster"] = "ALL^C|S^bool^^";
lintVal["clusterrank"] = "dot^G^clusterMode^^";
lintVal["color"] = "ALL^E|N|C^color|colorList^^";
lintVal["colorscheme"] = "ALL^E|N|C|G^string^^";
lintVal["comment"] = "ALL^E|N|G^string^^";
lintVal["compound"] = "dot^G^bool^^";
lintVal["concentrate"] = "ALL^G^bool^^";
lintVal["constraint"] = "dot^E^bool^^";
lintVal["decorate"] = "ALL^E^bool^^";
lintVal["defaultdist"] = "neato^G^double^epsilon^";
lintVal["dim"] = "neato|fdp|sfdp^G^int^2^";
lintVal["dimen"] = "neato|fdp|sfdp^G^int^2^";
lintVal["dir"] = "ALL^E^dirType^^";
lintVal["diredgeconstraints"] = "neato^G^string|bool^^";
lintVal["distortion"] = "ALL^N^double^-100.0^";
lintVal["dpi"] = "ALL^G^double^^";
lintVal["edgeURL"] = "ALL^E^escString^^";
lintVal["edgehref"] = "ALL^E^escString^^";
lintVal["edgetarget"] = "ALL^E^escString^^";
lintVal["edgetooltip"] = "ALL^E^escString^^";
lintVal["epsilon"] = "neato^G^double^^";
lintVal["esep"] = "neato|fdp|sfdp|circo|twopi^G^addDouble|addPoint^^";
lintVal["fillcolor"] = "ALL^N|E|C^color|colorList^^";
lintVal["fixedsize"] = "ALL^N^bool|string^^";
lintVal["fontcolor"] = "ALL^E|N|G|C^color^^";
lintVal["fontname"] = "ALL^E|N|G|C^string^^";
lintVal["fontnames"] = "ALL^G^string^^";
lintVal["fontpath"] = "ALL^G^string^^";
lintVal["fontsize"] = "ALL^E|N|G|C^double^1.0^";
lintVal["forcelabels"] = "ALL^G^bool^^";
lintVal["gradientangle"] = "ALL^N|C|G^int^0^";
lintVal["group"] = "dot^N^string^^";
lintVal["headURL"] = "ALL^E^escString^^";
lintVal["head_lp"] = "ALL^E^point^^";
lintVal["headclip"] = "ALL^E^bool^^";
lintVal["headhref"] = "ALL^E^escString^^";
lintVal["headlabel"] = "ALL^E^lblString^^";
lintVal["headport"] = "ALL^E^portPos^^";
lintVal["headtarget"] = "ALL^E^escString^^";
lintVal["headtooltip"] = "ALL^E^escString^^";
lintVal["height"] = "ALL^N^double^0.02^";
lintVal["href"] = "ALL^G|C|N|E^escString^^";
lintVal["id"] = "ALL^G|C|N|E^escString^^";
lintVal["image"] = "ALL^N^string^^";
lintVal["imagepath"] = "ALL^G^string^^";
lintVal["imagepos"] = "ALL^N^string^^";
lintVal["imagescale"] = "ALL^N^bool|string^^";
lintVal["inputscale"] = "neato|fdp^G^double^^";
lintVal["label"] = "ALL^E|N|G|C^lblString^^";
lintVal["labelURL"] = "ALL^E^escString^^";
lintVal["label_scheme"] = "sfdp^G^int^0^";
lintVal["labelangle"] = "ALL^E^double^-180.0^";
lintVal["labeldistance"] = "ALL^E^double^0.0^";
lintVal["labelfloat"] = "ALL^E^bool^^";
lintVal["labelfontcolor"] = "ALL^E^color^^";
lintVal["labelfontname"] = "ALL^E^string^^";
lintVal["labelfontsize"] = "ALL^E^double^1.0^";
lintVal["labelhref"] = "ALL^E^escString^^";
lintVal["labeljust"] = "ALL^G|C^string^^";
lintVal["labelloc"] = "ALL^N|G|C^string^^";
lintVal["labeltarget"] = "ALL^E^escString^^";
lintVal["labeltooltip"] = "ALL^E^escString^^";
lintVal["landscape"] = "ALL^G^bool^^";
lintVal["layer"] = "ALL^E|N|C^layerRange^^";
lintVal["layerlistsep"] = "ALL^G^string^^";
lintVal["layers"] = "ALL^G^layerList^^";
lintVal["layerselect"] = "ALL^G^layerRange^^";
lintVal["layersep"] = "ALL^G^string^^";
lintVal["layout"] = "ALL^G^string^^";
lintVal["len"] = "neato|fdp^E^double^^";
lintVal["levels"] = "sfdp^G^int^0.0^";
lintVal["levelsgap"] = "neato^G^double^^";
lintVal["lhead"] = "dot^E^string^^";
lintVal["lheight"] = "ALL^G|C^double^^";
lintVal["linelength"] = "ALL^G^int^60^";
lintVal["lp"] = "ALL^E|G|C^point^^";
lintVal["ltail"] = "dot^E^string^^";
lintVal["lwidth"] = "ALL^G|C^double^^";
lintVal["margin"] = "ALL^N|C|G^double|point^^";
lintVal["maxiter"] = "neato|fdp^G^int^^";
lintVal["mclimit"] = "dot^G^double^^";
lintVal["mindist"] = "circo^G^double^0.0^";
lintVal["minlen"] = "dot^E^int^0^";
lintVal["mode"] = "neato^G^string^^";
lintVal["model"] = "neato^G^string^^";
lintVal["newrank"] = "dot^G^bool^^";
lintVal["nodesep"] = "ALL^G^double^0.02^";
lintVal["nojustify"] = "ALL^G|C|N|E^bool^^";
lintVal["normalize"] = "neato|fdp|sfdp|twopi|circo^G^double|bool^^";
lintVal["notranslate"] = "neato^G^bool^^";
lintVal["nslimit"] = "dot^G^double^^";
lintVal["nslimit1"] = "dot^G^double^^";
lintVal["oneblock"] = "circo^G^bool^^";
lintVal["ordering"] = "dot^G|N^string^^";
lintVal["orientation"] = "ALL^N|G^double|string^-360.0^";
lintVal["outputorder"] = "ALL^G^outputMode^^";
lintVal["overlap"] = "fdp|neato|sfdp|circo|twopi^G^string|bool^^";
lintVal["overlap_scaling"] = "neato|sfdp|fdp|circo|twopi^G^double^-1.0e10^";
lintVal["overlap_shrink"] = "ALL^G^bool^^";
lintVal["pack"] = "ALL^G^bool|int^^";
lintVal["packmode"] = "ALL^G^packMode^^";
lintVal["pad"] = "ALL^G^double|point^^";
lintVal["page"] = "ALL^G^double|point^^";
lintVal["pagedir"] = "ALL^G^pagedir^^";
lintVal["pencolor"] = "ALL^C^color^^";
lintVal["penwidth"] = "ALL^C|N|E^double^0.0^";
lintVal["peripheries"] = "ALL^N|C^int^0^";
lintVal["pin"] = "neato|fdp^N^bool^^";
lintVal["pos"] = "neato|fdp^E|N^point|splineType^^";
lintVal["quadtree"] = "sfdp^G^quadType|bool^^";
lintVal["quantum"] = "ALL^G^double^0.0^";
lintVal["rank"] = "dot^S^rankType^^";
lintVal["rankdir"] = "dot^G^rankdir^^";
lintVal["ranksep"] = "dot|twopi^G^double|doubleList^0.02^";
lintVal["ratio"] = "ALL^G^double|string^^";
lintVal["rects"] = "ALL^N^rect^^";
lintVal["regular"] = "ALL^N^bool^^";
lintVal["remincross"] = "dot^G^bool^^";
lintVal["repulsiveforce"] = "sfdp^G^double^0.0^";
lintVal["resolution"] = "ALL^G^double^^";
lintVal["root"] = "twopi|circo^G|N^string|bool^^";
lintVal["rotate"] = "ALL^G^int^^";
lintVal["rotation"] = "sfdp^G^double^^";
lintVal["samehead"] = "dot^E^string^^";
lintVal["sametail"] = "dot^E^string^^";
lintVal["samplepoints"] = "ALL^N^int^^";
lintVal["scale"] = "neato|twopi^G^double|point^^";
lintVal["searchsize"] = "dot^G^int^^";
lintVal["sep"] = "fdp|neato|sfdp|circo|twopi^G^addDouble|addPoint^^";
lintVal["shape"] = "ALL^N^shape^^";
lintVal["shapefile"] = "ALL^N^string^^";
lintVal["showboxes"] = "dot^E|N|G^int^0^";
lintVal["sides"] = "ALL^N^int^0^";
lintVal["size"] = "ALL^G^double|point^^";
lintVal["skew"] = "ALL^N^double^-100.0^";
lintVal["smoothing"] = "sfdp^G^smoothType^^";
lintVal["sortv"] = "ALL^G|C|N^int^0^";
lintVal["splines"] = "ALL^G^bool|string^^";
lintVal["start"] = "neato|fdp|sfdp^G^startType^^";
lintVal["style"] = "ALL^E|N|C|G^style^^";
lintVal["stylesheet"] = "ALL^G^string^^";
lintVal["tailURL"] = "ALL^E^escString^^";
lintVal["tail_lp"] = "ALL^E^point^^";
lintVal["tailclip"] = "ALL^E^bool^^";
lintVal["tailhref"] = "ALL^E^escString^^";
lintVal["taillabel"] = "ALL^E^lblString^^";
lintVal["tailport"] = "ALL^E^portPos^^";
lintVal["tailtarget"] = "ALL^E^escString^^";
lintVal["tailtooltip"] = "ALL^E^escString^^";
lintVal["target"] = "ALL^E|N|G|C^escString|string^^";
lintVal["tooltip"] = "ALL^N|E|C|G^escString^^";
lintVal["truecolor"] = "ALL^G^bool^^";
lintVal["vertices"] = "ALL^N^pointList^^";
lintVal["viewport"] = "ALL^G^viewPort^^";
lintVal["voro_margin"] = "neato|fdp|sfdp|twopi|circo^G^double^0.0^";
lintVal["weight"] = "ALL^E^int|double^0(dot,twopi)|1(neato,fdp)^";
lintVal["width"] = "ALL^N^double^0.01^";
lintVal["xdotversion"] = "ALL^G^string^^";
lintVal["xlabel"] = "ALL^E|N^lblString^^";
lintVal["xlp"] = "ALL^N|E^point^^";
lintVal["z"] = "ALL^N^double^-MAXFLOAT|-1000^";
//////////////////////////////////////////////////////////////////////
void message(string messageMsg) {
//if (messageCnt==0) {
//print("/***********************************************************************");
//}
messageCnt++;
print(messageMsg);
}
//////////////////////////////////////////////////////////////////////
// not sure about empty string. Is it a boolean? Probably
int boolTest(string bString) { // see mapbool in utils.c
int rc;
if (tolower(bString)=="@(true|false|yes|no|0|1)")
rc=1; // good boolean
else if (bString=="@(|+([0-9]))")
rc=-1; // valid, but maybe a mistake
else
rc=0; // invalid, interpreted as false
return rc;
}
//////////////////////////////////////////////////////////////////////
int intTest(string bString) {
int rc;
if (bString=="?([+-])+([0-9])")
rc=1; // good
else
rc=0; // invalid, interpreted as false
return rc;
}
//////////////////////////////////////////////////////////////////////
int doubleTest(string bString) {
int rc;
if (bString==Double)
rc=1; // good
else
rc=0; // invalid, interpreted as false
return rc;
}
//////////////////////////////////////////////////////////////////////
int rankdirTest(string bString) {
int rc;
if (tolower(bString)=="tb|bt|lr|rl")
rc=1; // good
else
rc=0; // invalid, interpreted as false
return rc;
}
//////////////////////////////////////////////////////////////////////
int rankTest(string bString) {
int rc;
if (tolower(bString)=="max|min|source|sink|same")
rc=1; // good
else
rc=0; // invalid, interpreted as false
return rc;
}
//////////////////////////////////////////////////////////////////////
int dirTest(string bString) {
int rc;
if (tolower(bString)=="forward|back|both|none")
rc=1; // good
else
rc=0; // invalid, interpreted as false
return rc;
}
//////////////////////////////////////////////////////////////////////
int splinesTest(string bString) {
int rc;
if (boolTest(bString) || tolower(bString)=="spline|line||none|curved|ortho|polyline|")
rc=1; // good
else
rc=0; // invalid, interpreted as false
return rc;
}
//////////////////////////////////////////////////////////////////////
int pointTest(string bString) {
int rc;
if (bString==PointBang)
rc=1; // good
else
rc=0; // invalid, interpreted as false
return rc;
}
//////////////////////////////////////////////////////////////////////
int rectTest(string bString) {
int rc;
if (bString==Rect)
rc=1; // good
else
rc=0; // invalid, interpreted as false
return rc;
}
//////////////////////////////////////////////////////////////////////
void checkObj(obj_t thisO, string objType) {
string theseAtts[int], thisVal, val2;
int thisA, ok;
unset(theseAtts);
switch(objType) {
case "G":
text1="(Root) graph";
text2="";
cnt=tokens(usedAtts["G"], theseAtts, "|");
break;
case "C":
text1="cluster";
text2=sprintf("(cluster: %s)", thisO.name);
cnt=tokens(usedAtts["G"], theseAtts, "|");
break;
case "S":
text1="subgraph";
text2=sprintf("(subgraph: %s)", thisO.name);
cnt=tokens(usedAtts["G"], theseAtts, "|");
break;
case "N":
text1="node";
text2=sprintf("(node: %s)", thisO.name);
cnt=tokens(usedAtts[objType], theseAtts, "|");
break;
case "E":
text1="edge";
text2=sprintf("(edge: %s)", thisO.name);
cnt=tokens(usedAtts[objType], theseAtts, "|");
break;
}
//print("// thisO: ", thisO);
for (theseAtts[thisA]) {
thisAttr=theseAtts[thisA];
//print("// thisA: ", thisA," thisAttr: ", thisAttr);
if (hasAttr(thisO, thisAttr) && aget(thisO, thisAttr)!="") {
thisVal=aget(thisO, thisAttr);
ok=1;
// is it defined?
if (match(Objects[thisAttr],objType)<0) {
//print("// thisAttr matched: ", thisAttr);
ok=0;
if (objType=="S|C") {
// skip if attribute inherited from parent
ok=0;
val2="";
if (hasAttr(thisO.parent, thisAttr))
val2=aget(thisO.parent, thisAttr);
if (val2!="" && thisVal==val2) {
ok=-1; // wrong, but inherited value (probably)
}
}
if(ok==0) {
if (skipPos==0 || thisAttr!="pos") {
msgStr=sprintf("\"%s\" is not a defined %s attribute for \"%s\". %s", thisAttr, text1, theEngine, text2);
key=objType + bar + thisAttr;
errCount[key]++;
if (errCount[key]<10)
message(msgStr);
}
}
}
if (ok==1 && reportAttributes==1) {
msgStr=sprintf("The \"%s\" attribute \"%s\" is used.", text1, thisAttr);
key=objType + bar +"OK" + bar + thisAttr;
usedCount[key]++;
if (usedCount[key]==1)
message(msgStr);
}
// correct type?
if (ok==1) {
ok=0;
unset(tok);
cnt=tokens(Types[thisAttr], tok, "|");
text3=Types[thisAttr];
for (tok[i]) {
switch(tok[i]) {
case "bool":
text3="bool";
if (boolTest(thisVal))
ok=1;
break;
case "int":
text3="integer";
if (intTest(thisVal))
ok=1;
break;
case "double":
text3="floating point (double)";
if (doubleTest(thisVal))
ok=1;
break;
case "point":
text3="double,double";
if (pointTest(thisVal))
ok=1;
break;
case "rect":
text3="double,double,double,double";
if (rectTest(thisVal))
ok=1;
break;
case "dirType":
text3="forward|back|both|none";
if (tolower(thisVal)==tolower(text3))
ok=1;
break;
case "rankdir":
text3="TB|BT|LR|RL";
if (tolower(thisVal)==tolower(text3))
ok=1;
break;
case "rankType":
text3="same|min|max|source|sink";
if (tolower(thisVal)==tolower(text3))
ok=1;
break;
case "clusterMode":
text3="local|global|none";
if (tolower(thisVal)==tolower(text3))
ok=1;
break;
case "addDouble":
case "addPoint":
case "escString":
case "lblString":
case "string": // infinite?
case "shape": // too many
case "smoothType":
case "style": // too many
case "color": // too many
case "colorList": // too many
case "arrowtype": // too many
case "layerList":
case "layerRange":
case "outputMode": // ??
case "packMode": // too many??
case "layerRange":
case "layerRange":
default:
ok=1;
}
//print("// testing ", tok[i]," ",ok);
if (ok==1) break;
}
if (ok!=1) {
msgStr=sprintf("Bad value for %s (%s) (%s = %s). Should be \"%s\".", text1, thisO.name, thisAttr, thisVal, text3);
key=objType + bar + "V" + bar + thisAttr ;
errCount[key]++;
if (errCount[key]<10)
message(msgStr);
}
}
// check minimum (maybe later check reasonable?)
if (ok==1) {
unset(tok);
cnt=tokens(Minimums[thisAttr], tok, "|");
if (cnt==1) { // for time being, only simple minimums
ok=0;
if ((float)thisVal < (float)tok[0]) {
msgStr=sprintf("Bad value for (%s) %s (%s = %s). Should be >= \"%s\".", text1, thisO.name, thisAttr, thisVal, tok[0]);
key=objType + bar + "M" + bar + thisAttr; // + "_M";
errCount[key]++;
if (errCount[key]<10)
message(msgStr);
}
}
}
}
}
}
//////////////////////////////////////////////////////////////////////
void setupChecks() {
string oChar, aChar[int];
aChar[1]="G";
aChar[2]="N";
aChar[3]="E";
for (aChar[i]) {
oChar=aChar[i];
for (thisAttr = fstAttr(Root,oChar); thisAttr != ""; thisAttr = nxtAttr(Root,oChar,thisAttr)) {
switch(oChar) {
case "G": // at this point, we can not distinguish G from C from S
usedAtts["G"]=usedAtts["G"]+thisAttr+"|";
usedAtts["C"]=usedAtts["C"]+thisAttr+"|";
usedAtts["S"]=usedAtts["S"]+thisAttr+"|";
break;
case "N":
usedAtts["N"]=usedAtts["N"]+thisAttr+"|";
break;
case "E":
usedAtts["E"]=usedAtts["E"]+thisAttr+"|";
break;
}
}
}
}
//////////////////////////////////////////////////////////////////////
graph_t graphTraverse(graph_t thisGraph) {
graph_t aGraph;
for (aGraph = fstsubg(thisGraph); aGraph; aGraph = nxtsubg(aGraph)) {
if (match(aGraph.name,"cluster")==0 || (hasAttr(aGraph, "cluster") && boolTest(aGraph.cluster)!=0)) {
objC[++Ci]=aGraph;
gType[aGraph]="C";
} else {
objS[++Si]=aGraph;
gType[aGraph]="S";
}
aGraph = graphTraverse(aGraph);
}
return thisGraph;
}
dfltEngine="dot"; // default
reportAttributes=0;
skipPos=0;
i=0;
while (i<ARGC) {
if (ARGV[i]=="H|[?]") { // help
printf(2,help);
exit(0);
} else if (ARGV[i]=="(dot|fdp|neato|twopi|circo|sfdp|patchwork)") {
dfltEngine=ARGV[i];
} else if (ARGV[i]=="A") {
reportAttributes=1;
} else if (ARGV[i]=="S") {
skipPos=1;
}
i++;
}
} // END OF BEGIN
BEG_G{
unset(usedAtts);
unset(errCount);
unset(usedCount);
unset(Engines);
unset(Objects);
unset(Types);
unset(tok);
unset(Minimums);
unset(objC);
unset(objS);
unset(objG);
unset(objN);
unset(objE);
Root=$G;
gType[$G]="G";
objG[1]=$G;
theEngine="";
print("/***********************************************************************");
if (hasAttr(Root, "layout") && Root.layout!="") {
theEngine=$.layout;
print(" the file sets layout to ", theEngine);
print("FILE: ", $F, " ENGINE: ", theEngine);
} else {
theEngine=dfltEngine; // default
print("FILE ", $F, " ENGINE: ", "not set, using ", dfltEngine);
}
messageCnt=0;
// rebuild tables (multiple input files might have different layout values)
for (lintVal[thisAttr]) {
split(lintVal[thisAttr], tok, caret);
Engines[thisAttr]= bar + tok[0] + bar;
Objects[thisAttr]= bar + tok[1] + bar;
Types[thisAttr]= bar + tok[2] + bar;
Minimums[thisAttr]=bar + tok[3] + bar;
}
for (Engines[thisAttr]) {
if (match(Engines[thisAttr],"ALL")<0 && match(Engines[thisAttr], theEngine)<0) {
unset(Engines, thisAttr);
unset(Objects, thisAttr);
unset(Types, thisAttr);
unset(Minimums, thisAttr);
}
}
graphTraverse($G);
setupChecks();
}
N{
objN[++Ni]=$;
}
E{
objE[++Ei]=$;
}
END_G{
string ts1, ts2, objType;
for (objG[i]) {
checkObj(objG[i], "G");
}
for (objC[i]) {
checkObj(objC[i], "C");
}
for (objS[i]) {
checkObj(objS[i], "S");
}
for (objN[i]) {
checkObj(objN[i], "N");
}
for (objE[i]) {
checkObj(objE[i], "E");
}
tooMany=0;
for (errCount[key]) {
if (errCount[key]>=tooMany) {
cnt=tokens(key, tok, bar);
objType=tok[0];
switch(objType) {
case "G":
text1="(Root) graph";
break;
case "C":
text1="clusters";
break;
case "S":
text1="subgraphs";
break;
case "N":
text1="nodes";
break;
case "E":
text1="edges";
break;
}
if (tok[1]!="OK"){
msgStr=sprintf("There is/are %i incorrect usage(s) of attribute \"%s\" for %s", errCount[key], tok[cnt-1], text1);
message(msgStr);
}
}
}
tooMany=10;
for (usedCount[key]) {
if (usedCount[key]>=tooMany) {
if (tok[0]=="N")
ts1="nodes";
else
ts1="edges";
if (tok[1]!="OK"){
msgStr=sprintf("There were a total of %i incorrect usage of attribute \"%s\" for %s", usedCount[key], tok[cnt-1], ts1);
message(msgStr);
}
}
}
//if (messageCnt>0) {
print("*******************************************************************/");
//}
messageCnt=0;
}
@steveroush
Copy link
Author

Bug fix - program got confused if invalid attribute was inherited from parent graph

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment