Doxygen
Loading...
Searching...
No Matches
dotdirdeps.cpp
Go to the documentation of this file.
1/******************************************************************************
2*
3* Copyright (C) 1997-2019 by Dimitri van Heesch.
4*
5* Permission to use, copy, modify, and distribute this software and its
6* documentation under the terms of the GNU General Public License is hereby
7* granted. No representations are made about the suitability of this software
8* for any purpose. It is provided "as is" without express or implied warranty.
9* See the GNU General Public License for more details.
10*
11* Documents produced by Doxygen are derivative works derived from the
12* input used in their production; they are not affected by this license.
13*
14*/
15
16#include <algorithm>
17#include <iterator>
18#include <utility>
19#include <cstdint>
20#include <math.h>
21#include <cassert>
22#include <map>
23#include <memory>
24#include <string>
25#include <vector>
26
27#include "dotdirdeps.h"
28#include "util.h"
29#include "doxygen.h"
30#include "config.h"
31#include "image.h"
32#include "dotnode.h"
33#include "textstream.h"
34
35
36using DirDefMap = std::map<std::string,const DirDef *>;
37
38/** Properties are used to format the directories in the graph distinctively. */
40{
41 bool isIncomplete = false; //!< true if not all successors of a cluster are drawn
42 bool isOrphaned = false; //!< true if parent is not drawn
43 bool isTruncated = false; //!< true has successors, none is drawn
44 bool isOriginal = false; //!< true if is the directory for which the graph is drawn
45 bool isPeripheral = false; //!< true if no successor of parent of original directory
46};
47
48/** Builder helper to create instances of the DotDirProperty struct */
50{
51 public:
53 DotDirPropertyBuilder &makeOrphaned (bool b=true) { m_property.isOrphaned =b; return *this; }
54 DotDirPropertyBuilder &makeTruncated (bool b=true) { m_property.isTruncated =b; return *this; }
55 DotDirPropertyBuilder &makeOriginal (bool b=true) { m_property.isOriginal =b; return *this; }
57 operator DotDirProperty() { return m_property; }
58 private:
60};
61
62/** Elements consist of (1) directory relation and (2) whether it is pointing only to inherited dependees. */
63typedef std::vector< std::pair< std::unique_ptr<DirRelation>, bool> > DirRelations;
64
65/** Returns a DOT color name according to the directory depth. */
67{
68 int hue = Config_getInt(HTML_COLORSTYLE_HUE);
69 int sat = Config_getInt(HTML_COLORSTYLE_SAT);
70 int gamma = Config_getInt(HTML_COLORSTYLE_GAMMA);
71 assert(depthIndex>=0 && depthIndex<=Config_getInt(DIR_GRAPH_MAX_DEPTH));
72 float fraction = static_cast<float>(depthIndex)/static_cast<float>(Config_getInt(DIR_GRAPH_MAX_DEPTH));
73 const char hex[] = "0123456789abcdef";
74 int range = 0x40; // range from darkest color to lightest color
75 int luma = 0xef-static_cast<int>(fraction*static_cast<float>(range)); // interpolation
76 double r=0, g=0, b=0;
77 ColoredImage::hsl2rgb(hue/360.0,sat/255.0,
78 pow(luma/255.0,gamma/100.0),&r,&g,&b);
79 int red = static_cast<int>(r*255.0);
80 int green = static_cast<int>(g*255.0);
81 int blue = static_cast<int>(b*255.0);
82 assert(red>=0 && red<=255);
83 assert(green>=0 && green<=255);
84 assert(blue>=0 && blue<=255);
85 char colStr[8];
86 colStr[0]='#';
87 colStr[1]=hex[red>>4];
88 colStr[2]=hex[red&0xf];
89 colStr[3]=hex[green>>4];
90 colStr[4]=hex[green&0xf];
91 colStr[5]=hex[blue>>4];
92 colStr[6]=hex[blue&0xf];
93 colStr[7]=0;
94 //printf("i=%d max=%d fraction=%f luma=%d %02x %02x %02x -> color=%s\n",
95 // depthIndex,Config_getInt(DIR_GRAPH_MAX_DEPTH),fraction,luma,red,green,blue,colStr);
96 return colStr;
97}
98
99/** Returns a DOT color name according to the directory properties. */
100static const char* getDirectoryBorderColor(const DotDirProperty &property)
101{
102 if (property.isTruncated && property.isOrphaned)
103 {
104 return "red";
105 }
106 else if (property.isTruncated)
107 {
108 return "red";
109 }
110 else if (property.isOrphaned)
111 {
112 return "grey50";
113 }
114 else
115 {
116 return "grey25";
117 }
118}
119
120/** Returns a DOT node style according to the directory properties. */
121static std::string getDirectoryBorderStyle(const DotDirProperty &property)
122{
123 std::string style = "filled";
124 if (property.isOriginal)
125 {
126 style += ",bold";
127 }
128 if (property.isIncomplete)
129 {
130 style += ",dashed";
131 }
132 else if (property.isTruncated && property.isOrphaned)
133 {
134 style += ",dashed";
135 }
136 return style;
137}
138
139static TextStream &common_attributes(TextStream &t, const DirDef *const dir, const DotDirProperty &prop)
140{
141 DString url = dir->getOutputFileBase();
143 return t <<
144 "style=\"" << getDirectoryBorderStyle(prop) << "\", "
145 "URL=\"" << url << "\","
146 "tooltip=\"" << escapeTooltip(dir->briefDescriptionAsTooltip()) << "\"";
147}
148
149/**
150 * Puts DOT code for drawing directory to stream and adds it to the list.
151 * @param[in,out] t stream to which the DOT code is written to
152 * @param[in] directory will be mapped to a node in DOT code
153 * @param[in] property are evaluated for formatting
154 * @param[in,out] directoriesInGraph lists the directories which have been written to the output stream
155 * @param[in] startLevel current level to calculate relative distances from to determine the background color
156 */
157static void drawDirectory(TextStream &t, const DirDef *const directory, const DotDirProperty &property,
158 DirDefMap &directoriesInGraph,int startLevel)
159{
160 t << " " << directory->getOutputFileBase() << " ["
161 "label=\"" << DotNode::convertLabel(directory->shortName()) << "\", "
162 "fillcolor=\"" << getDirectoryBackgroundColor(directory->level()-startLevel) << "\", "
163 "color=\"" << getDirectoryBorderColor(property) << "\", ";
164 common_attributes(t, directory, property)
165 << "];\n";
166 directoriesInGraph.emplace(directory->getOutputFileBase().str(), directory);
167}
168
169/** Checks, if the directory is a the maximum drawn directory level. */
170static bool isAtMaxDepth(const DirDef *const directory, const int startLevel)
171{
172 return (directory->level() - startLevel) >= Config_getInt(DIR_GRAPH_MAX_DEPTH);
173}
174
175/**
176 * Writes DOT code for opening a cluster subgraph to stream.
177 *
178 * Ancestor clusters directly get a label. Other clusters get a plain text node with a label instead.
179 * This is because the plain text node can be used to draw dependency relationships.
180 */
181static void drawClusterOpening(TextStream &outputStream, const DirDef *const directory,
182 const DotDirProperty &directoryProperty, DirDefMap &directoriesInGraph, const bool isAncestor,int startLevel)
183{
184 outputStream << " subgraph cluster" << directory->getOutputFileBase() << " {\n"
185 " graph [ "
186 "bgcolor=\"" << getDirectoryBackgroundColor(directory->level()-startLevel) << "\", "
187 "pencolor=\"" << getDirectoryBorderColor(directoryProperty) << "\", "
188 "label=\"";
189 if (isAncestor)
190 {
191 outputStream << DotNode::convertLabel(directory->shortName());
192 }
193 outputStream << "\", "
194 << Config_getString(DOT_COMMON_ATTR) << " ";
195 common_attributes(outputStream, directory, directoryProperty)
196 << "]\n";
197 if (!isAncestor)
198 {
199 outputStream << " " << directory->getOutputFileBase() << " [shape=plaintext, "
200 "label=\"" << DotNode::convertLabel(directory->shortName()) << "\""
201 "];\n";
202 directoriesInGraph.emplace(directory->getOutputFileBase().str(), directory);
203 }
204}
205
207{
208 t << " }\n";
209}
210
211/**
212 * Assembles a list of the directory relations and whether or not they result from "inheritance".
213 * @param dependencies Array to add the dependencies to.
214 * @param srcDir is the source of the dependency.
215 * @param isLeaf true, if no children are drawn for this directory.
216 */
217static void addDependencies(DirRelations &dependencies,const DirDef *const srcDir, bool isLeaf)
218{
219 for (const auto &usedDirectory : srcDir->usedDirs())
220 {
221 const auto &dstDir = usedDirectory->dir();
222 if (!dstDir->isParentOf(srcDir) && (isLeaf || usedDirectory->hasDirectSrcDeps()))
223 {
224 DString relationName;
225 relationName.sprintf("dir_%06d_%06d", srcDir->dirIndex(), dstDir->dirIndex());
226 bool directRelation = isLeaf ? usedDirectory->hasDirectDstDeps() : usedDirectory->hasDirectDeps();
227 dependencies.emplace_back(
228 std::make_unique<DirRelation>(relationName, srcDir, usedDirectory.get()),
229 directRelation);
230 }
231 }
232}
233
234/** Recursively draws directory tree. */
235static void drawTree(DirRelations &dependencies, TextStream &t, const DirDef *const directory,
236 int startLevel, DirDefMap &directoriesInGraph, const bool isTreeRoot)
237{
238 if (!directory->hasSubdirs())
239 {
240 const DotDirProperty directoryProperty = DotDirPropertyBuilder().makeOriginal(isTreeRoot);
241 drawDirectory(t, directory, directoryProperty, directoriesInGraph,startLevel);
242 addDependencies(dependencies, directory, true);
243 }
244 else
245 {
246 if (isAtMaxDepth(directory, startLevel)) // maximum nesting level reached
247 {
248 const DotDirProperty directoryProperty = DotDirPropertyBuilder().makeOriginal(isTreeRoot);
249 drawDirectory(t, directory, directoryProperty, directoriesInGraph,startLevel);
250 addDependencies(dependencies, directory, true);
251 }
252 else // start a new nesting level
253 {
254 // open cluster
255 {
256 const DotDirProperty directoryProperty = DotDirPropertyBuilder().makeOriginal(isTreeRoot);
257 drawClusterOpening(t, directory, directoryProperty, directoriesInGraph, false, startLevel);
258 addDependencies(dependencies, directory, false);
259 }
260
261 // process all sub directories
262 for (const auto &subDirectory : directory->subDirs())
263 {
264 drawTree(dependencies, t, subDirectory, startLevel, directoriesInGraph, false);
265 }
266
267 // close cluster
268 {
270 }
271 }
272 }
273}
274
275/**
276 * Write DOT code for directory dependency graph.
277 *
278 * Code is generated for a directory. Successors (sub-directories) of this directory are recursively drawn.
279 * Recursion is limited by `DIR_GRAPH_MAX_DEPTH`. The dependencies of those directories
280 * are drawn.
281 *
282 * If a dependee is not part of directory tree above, then the dependency is drawn to the first parent of the
283 * dependee, whose parent is an ancestor (sub-directory) of the original directory.
284 *
285 * @param t stream where the DOT code is written to
286 * @param dd directory for which the graph is generated for
287 * @param linkRelations if true, hyperlinks to the list of file dependencies are added
288 */
289void writeDotDirDepGraph(TextStream &t,const DirDef *dd,bool linkRelations)
290{
291 DirDefMap dirsInGraph;
292
293 dirsInGraph.emplace(dd->getOutputFileBase().str(),dd);
294
295 std::vector<const DirDef *> usedDirsNotDrawn, usedDirsDrawn;
296 for (const auto& usedDir : dd->usedDirs())
297 {
298 usedDirsNotDrawn.push_back(usedDir->dir());
299 }
300
301 auto moveDrawnDirs = [&usedDirsDrawn,&usedDirsNotDrawn](const std::vector<const DirDef *>::iterator &newEnd)
302 {
303 // usedDirsNotDrawn is split into two segments: [begin()....newEnd-1] and [newEnd....end()]
304 // where the second segment starting at newEnd has been drawn, so append this segment to the usedDirsDrawn list and
305 // remove it from the usedDirsNotDrawn list.
306 std::move(newEnd, std::end(usedDirsNotDrawn), std::back_inserter(usedDirsDrawn));
307 usedDirsNotDrawn.erase(newEnd, usedDirsNotDrawn.end());
308 };
309
310 // if dd has a parent draw it as the outer layer
311 const auto &parent = dd->parent();
312 if (parent)
313 {
314 const DotDirProperty parentDirProperty = DotDirPropertyBuilder().
315 makeIncomplete().
316 makeOrphaned(parent->parent()!=nullptr);
317 drawClusterOpening(t, parent, parentDirProperty, dirsInGraph, true, parent->level());
318
319 {
320 // draw all directories which have `dd->parent()` as parent and `dd` as dependent
321 const auto &newEnd = std::stable_partition(usedDirsNotDrawn.begin(), usedDirsNotDrawn.end(),
322 [&](const DirDef *const usedDir)
323 {
324 if (dd!=usedDir && dd->parent()==usedDir->parent()) // usedDir and dd share the same parent
325 {
326 const DotDirProperty usedDirProperty = DotDirPropertyBuilder().makeTruncated(usedDir->hasSubdirs());
327 drawDirectory(t, usedDir, usedDirProperty, dirsInGraph, parent->level());
328 return false; // part of the drawn partition
329 }
330 return true; // part of the not-drawn partition
331 });
332 moveDrawnDirs(newEnd);
333 }
334 }
335
336 // draw the directory tree with dd as root
337 DirRelations dependencies;
338 drawTree(dependencies, t, dd, dd->level(), dirsInGraph, true);
339
340 if (parent)
341 {
343 }
344
345 // add nodes for other used directories (i.e. outside of the cluster of directories directly connected to dd)
346 {
347 const auto &newEnd = std::stable_partition(usedDirsNotDrawn.begin(), usedDirsNotDrawn.end(),
348 [&](const DirDef *const usedDir) // for each used dir (=directly used or a parent of a directly used dir)
349 {
350 const DirDef *dir=dd;
351 while (dir)
352 {
353 if (dir!=usedDir && dir->parent()==usedDir->parent()) // include if both have the same parent (or no parent)
354 {
355 const DotDirProperty usedDirProperty = DotDirPropertyBuilder().
356 makeOrphaned(usedDir->parent()!=nullptr).
357 makeTruncated(usedDir->hasSubdirs()).
358 makePeripheral();
359 drawDirectory(t, usedDir, usedDirProperty, dirsInGraph, dir->level());
360 return false; // part of the drawn partition
361 }
362 dir=dir->parent();
363 }
364 return true; // part of the not-drawn partition
365 });
366 moveDrawnDirs(newEnd);
367 }
368
369 // add relations between all selected directories
370 {
371 for (const auto &relationPair : dependencies)
372 {
373 const auto &relation = relationPair.first;
374 const bool directRelation = relationPair.second;
375 const auto &udir = relation->destination();
376 const auto &usedDir = udir->dir();
377 const bool destIsSibling = std::find(std::begin(usedDirsDrawn), std::end(usedDirsDrawn), usedDir) != std::end(usedDirsDrawn);
378 const bool destIsDrawn = dirsInGraph.find(usedDir->getOutputFileBase().str())!=dirsInGraph.end(); // only point to nodes that are in the graph
379 const bool atMaxDepth = isAtMaxDepth(usedDir, dd->level());
380
381 if (destIsSibling || (destIsDrawn && (directRelation || atMaxDepth)))
382 {
383 const auto &relationName = relation->getOutputFileBase();
384 const auto &dir = relation->source();
385 Doxygen::dirRelations.add(relationName,
386 std::make_unique<DirRelation>(
387 relationName,dir,udir));
388 size_t nrefs = udir->filePairs().size();
389 t << " " << dir->getOutputFileBase() << "->"
390 << usedDir->getOutputFileBase();
391 t << " [headlabel=\"" << nrefs << "\", labeldistance=1.5";
392 if (linkRelations)
393 {
394 DString fn = relationName;
396 t << " headhref=\"" << fn << "\"";
397 t << " href=\"" << fn << "\"";
398 }
399 t << " color=\"steelblue1\" fontcolor=\"steelblue1\"];\n";
400 }
401 }
402 }
403}
404
406{
407}
408
412
414{
415 return m_dir->getOutputFileBase()+"_dep";
416
417}
418
420{
421 // compute md5 checksum of the graph were are about to generate
422 TextStream md5stream;
423 writeGraphHeader(md5stream, m_dir->displayName());
424 md5stream << " compound=true\n";
426 writeGraphFooter(md5stream);
427 m_theGraph = md5stream.str();
428}
429
434
439
441 const DString &path, const DString &fileName, const DString &relPath, bool generateImageMap,
442 int graphId, bool linkRelations)
443{
444 m_linkRelations = linkRelations;
445 m_urlOnly = true;
446
448
449 return DotGraph::writeGraph(out, graphFormat, textFormat, path, fileName, relPath, generateImageMap, graphId);
450}
451
453{
454 return m_dir->depGraphIsTrivial();
455}
static void hsl2rgb(double h, double s, double l, double *pRed, double *pGreen, double *pBlue)
Definition image.cpp:368
A String class for use with Doxygen wrapping std::string and adding some additional functionality off...
Definition dstring.h:88
DString & sprintf(const char *format,...)
Definition dstring.cpp:30
const std::string & str() const
Definition dstring.h:649
virtual DString briefDescriptionAsTooltip() const =0
virtual DString displayName(bool includeScope=true) const =0
virtual DString getOutputFileBase() const =0
A model of a directory symbol.
Definition dirdef.h:108
virtual int level() const =0
virtual const DString shortName() const =0
virtual int dirIndex() const =0
virtual DirDef * parent() const =0
virtual bool depGraphIsTrivial() const =0
virtual const DirList & subDirs() const =0
virtual const UsedDirLinkedMap & usedDirs() const =0
virtual bool hasSubdirs() const =0
DString getOutputFileBase() const
Definition dirdef.h:163
DString getImgAltText() const override
DString writeGraph(TextStream &out, GraphOutputFormat gf, EmbeddedOutputFormat ef, const DString &path, const DString &fileName, const DString &relPath, bool writeImageMap=true, int graphId=-1, bool linkRelations=true)
~DotDirDeps() override
void computeTheGraph() override
const DirDef * m_dir
Definition dotdirdeps.h:51
DString getBaseName() const override
bool isTrivial() const
bool m_linkRelations
Definition dotdirdeps.h:53
DotDirDeps(const DirDef *dir)
DString getMapLabel() const override
Builder helper to create instances of the DotDirProperty struct.
DotDirPropertyBuilder & makeOriginal(bool b=true)
DotDirProperty m_property
DotDirPropertyBuilder & makePeripheral(bool b=true)
DotDirPropertyBuilder & makeOrphaned(bool b=true)
DotDirPropertyBuilder & makeIncomplete(bool b=true)
DotDirPropertyBuilder & makeTruncated(bool b=true)
DString m_baseName
Definition dotgraph.h:94
DString m_theGraph
Definition dotgraph.h:95
static void writeGraphFooter(TextStream &t)
Definition dotgraph.cpp:294
bool m_urlOnly
Definition dotgraph.h:100
bool m_doNotAddImageToIndex
Definition dotgraph.h:97
DString writeGraph(TextStream &t, GraphOutputFormat gf, EmbeddedOutputFormat ef, const DString &path, const DString &fileName, const DString &relPath, bool writeImageMap=true, int graphId=-1)
Definition dotgraph.cpp:114
static void writeGraphHeader(TextStream &t, const DString &title=DString())
Definition dotgraph.cpp:269
static DString convertLabel(const DString &, LabelStyle=LabelStyle::Plain)
Definition dotnode.cpp:196
static DirRelationLinkedMap dirRelations
Definition doxygen.h:121
T * add(const char *k, Args &&... args)
Definition linkedmap.h:90
Text streaming class that buffers data.
Definition textstream.h:36
std::string str() const
Return the contents of the buffer as a std::string object.
Definition textstream.h:232
#define Config_getInt(name)
Definition config.h:34
#define Config_getString(name)
Definition config.h:32
constexpr DocNodeVariant * parent(DocNodeVariant *n)
returns the parent node of a given node n or nullptr if the node has no parent.
Definition docnode.h:1335
std::vector< std::pair< std::unique_ptr< DirRelation >, bool > > DirRelations
Elements consist of (1) directory relation and (2) whether it is pointing only to inherited dependees...
static TextStream & common_attributes(TextStream &t, const DirDef *const dir, const DotDirProperty &prop)
static bool isAtMaxDepth(const DirDef *const directory, const int startLevel)
Checks, if the directory is a the maximum drawn directory level.
static void drawClusterClosing(TextStream &t)
static void addDependencies(DirRelations &dependencies, const DirDef *const srcDir, bool isLeaf)
Assembles a list of the directory relations and whether or not they result from "inheritance".
static std::string getDirectoryBorderStyle(const DotDirProperty &property)
Returns a DOT node style according to the directory properties.
static void drawDirectory(TextStream &t, const DirDef *const directory, const DotDirProperty &property, DirDefMap &directoriesInGraph, int startLevel)
Puts DOT code for drawing directory to stream and adds it to the list.
static void drawClusterOpening(TextStream &outputStream, const DirDef *const directory, const DotDirProperty &directoryProperty, DirDefMap &directoriesInGraph, const bool isAncestor, int startLevel)
Writes DOT code for opening a cluster subgraph to stream.
std::map< std::string, const DirDef * > DirDefMap
static void drawTree(DirRelations &dependencies, TextStream &t, const DirDef *const directory, int startLevel, DirDefMap &directoriesInGraph, const bool isTreeRoot)
Recursively draws directory tree.
void writeDotDirDepGraph(TextStream &t, const DirDef *dd, bool linkRelations)
Write DOT code for directory dependency graph.
static DString getDirectoryBackgroundColor(int depthIndex)
Returns a DOT color name according to the directory depth.
static const char * getDirectoryBorderColor(const DotDirProperty &property)
Returns a DOT color name according to the directory properties.
EmbeddedOutputFormat
Definition dotgraph.h:30
GraphOutputFormat
Definition dotgraph.h:29
DString escapeTooltip(const DString &tooltip)
Definition dotnode.cpp:99
Properties are used to format the directories in the graph distinctively.
bool isTruncated
true has successors, none is drawn
bool isOrphaned
true if parent is not drawn
bool isOriginal
true if is the directory for which the graph is drawn
bool isPeripheral
true if no successor of parent of original directory
bool isIncomplete
true if not all successors of a cluster are drawn
void addHtmlExtensionIfMissing(DString &fName)
Definition util.cpp:3933
DString convertToXML(const DString &s, bool keepEntities, const bool citeEntry)
Definition util.cpp:3234
DString escapeCharsInString(const DString &name, bool allowDots, bool allowUnderscore)
Definition util.cpp:2690
A bunch of utility functions.