Last active
June 1, 2024 00:44
-
-
Save steveroush/b020b29785704dbf37f79fbf02b3271f to your computer and use it in GitHub Desktop.
gvlint - a "lint" program for Graphviz files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************** | |
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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Bug fix - program got confused if invalid attribute was inherited from parent graph