- provide as ''operator'' on the TestChainLink instance - show shortened Node-Hash as label on each Node
248 lines
6.9 KiB
C++
248 lines
6.9 KiB
C++
/*
|
|
DOT-GEN.hpp - DSL to generate Graphviz-DOT code
|
|
|
|
Copyright (C) Lumiera.org
|
|
2023, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of
|
|
the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
/** @file dot-gen.hpp
|
|
** Support for generation of Graphviz-DOT code for structure visualisation.
|
|
** The [dot language] offers a simple notation to represent structural information
|
|
** as diagrams of abstract graphs and networks. The *Graphviz* layout programs
|
|
** translate these into automatically arranged diagrams graphs, relying on basic
|
|
** layout schemes like directed and undirected graphs, force-directed placement,
|
|
** radial arrangements, clustered graphs or squarified treemap layout. These
|
|
** visualisations can be rendered as images, vector graphic SVG or PDF and
|
|
** a lot of further formats.
|
|
**
|
|
** The namespace lib::dot_gen contains a set of integrated builder DSL functions
|
|
** to simplify the task of syntax generation; notable it is possible to set up
|
|
** several [sections](\ref lib::dot_gen::Section), which can then be gradually
|
|
** populated with definition clauses while traversing a data structure.
|
|
**
|
|
** @todo 11/2023 this is an initial draft, shaped by the immediate need to visualise
|
|
** [random generated](\ref vault::gear::test::TestChainLoad) computation
|
|
** patterns for Scheduler [load testing](\ref SchedulerStress_test).
|
|
** The abstraction level of this DSL is low and structures closely match
|
|
** some clauses of the DOT language; this approach may not yet be adequate
|
|
** to generate more complex graph structures and was extracted as a starting
|
|
** point for further refinements.
|
|
**
|
|
** ## Usage
|
|
** The top-level entrance point is lib::dot_get::digraph(), allowing to combine a
|
|
** series of lib::dot_gen::Section definitions into a DOT script, which can then
|
|
** be retrieved by string conversion (or sent to standard output)
|
|
** - Section is an accumulator of lines with DOT language specs
|
|
** - Code is a string with syntax, used as base for some pre-configured terms
|
|
** - Node defines a variable name, but can be augmented with attributes to
|
|
** build a _node-statement_
|
|
** - Scope is meant as a device to group several nodes together, typically to
|
|
** form a cluster or stratum in the generated layout
|
|
**
|
|
** @see TestChainLoad_test
|
|
** @see SchedulerStress_test
|
|
** [dot language]: https://graphviz.org/doc/info/lang.html
|
|
*/
|
|
|
|
|
|
#ifndef LIB_DOT_GEN_H
|
|
#define LIB_DOT_GEN_H
|
|
|
|
|
|
#include "lib/format-util.hpp"
|
|
#include "lib/util.hpp"
|
|
|
|
#include <utility>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
|
|
namespace lib {
|
|
namespace dot_gen { ///< Collection of builder DSL functions to generate Graphviz-DOT code
|
|
|
|
using util::toString;
|
|
using util::isnil;
|
|
using std::string;
|
|
using std::move;
|
|
|
|
|
|
/** markup to generate a piece of code */
|
|
struct Code : string
|
|
{
|
|
using string::string;
|
|
Code(string const& c) : string{c} { }
|
|
Code(string && c) : string{move(c)}{ }
|
|
};
|
|
|
|
|
|
/** Accumulator to collect lines of DOT code */
|
|
struct Section
|
|
{
|
|
std::vector<string> lines;
|
|
|
|
Section (string name)
|
|
: lines{"// "+name}
|
|
{ }
|
|
|
|
Section&&
|
|
operator+= (Code const& code)
|
|
{
|
|
lines.emplace_back(code);
|
|
return move(*this);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/** generate a Node name or a node_statement
|
|
* defining attributes of that node. All variables
|
|
* use the format `N<number>`. */
|
|
struct Node : Code
|
|
{
|
|
Node (size_t id)
|
|
: Code{"N"+toString(id)}
|
|
{ }
|
|
|
|
Node&&
|
|
addAttrib (string def)
|
|
{
|
|
if (back() != ']')
|
|
append ("[");
|
|
else
|
|
{
|
|
resize (length()-2);
|
|
append (", ");
|
|
}
|
|
append (def+" ]");
|
|
return move(*this);
|
|
}
|
|
|
|
Node&&
|
|
label (string text)
|
|
{
|
|
return addAttrib ("label=\""+text+"\"");
|
|
}
|
|
|
|
Node&&
|
|
style (Code const& code)
|
|
{
|
|
if (not isnil(code))
|
|
addAttrib (code);
|
|
return move(*this);
|
|
}
|
|
};
|
|
|
|
|
|
/** accumulator to collect nodes grouped into a scope */
|
|
struct Scope : Code
|
|
{
|
|
Scope (size_t id)
|
|
: Code{"{ /*"+toString(id)+"*/ }"}
|
|
{ }
|
|
|
|
Scope&&
|
|
add (Code const& code)
|
|
{
|
|
resize(length()-1);
|
|
append (code+" }");
|
|
return move(*this);
|
|
}
|
|
|
|
Scope&&
|
|
rank (string rankSetting)
|
|
{
|
|
return add(Code{"rank="+rankSetting});
|
|
}
|
|
};
|
|
|
|
inline Node node (size_t id) { return Node(id); }
|
|
inline Scope scope (size_t id) { return Scope(id); }
|
|
|
|
/** generate a directed node connectivity clause */
|
|
inline Code
|
|
connect (size_t src, size_t dest)
|
|
{
|
|
return Code{Node(src) +" -> "+ Node(dest)};
|
|
}
|
|
|
|
|
|
/** Helper to collect DOT-Graphviz code for output */
|
|
class DotOut
|
|
{
|
|
std::ostringstream buff_;
|
|
|
|
static uint const IDENT_STEP = 2;
|
|
public:
|
|
void
|
|
putLine (string line, uint indent=0)
|
|
{
|
|
if (indent)
|
|
buff_ << string(indent,' ');
|
|
buff_ << line
|
|
<< '\n';
|
|
}
|
|
|
|
void
|
|
put (Code const& code)
|
|
{
|
|
buff_ << code;
|
|
}
|
|
|
|
void
|
|
put (Section const& sect)
|
|
{
|
|
for (string const& line : sect.lines)
|
|
putLine (line, IDENT_STEP);
|
|
}
|
|
|
|
template<class P, class...PS>
|
|
void
|
|
put (P const& part, PS const& ...parts)
|
|
{
|
|
put (part);
|
|
putLine ("");
|
|
put (parts...);
|
|
}
|
|
|
|
/** retrieve complete code generated thus far */
|
|
operator string() const
|
|
{
|
|
return buff_.str();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Entrance-point: generate a graph spec in DOT-Language.
|
|
* @param parts a sequence of Section or Code objects to be combined and rendered
|
|
* @return DotOut object holding the script rendered into a stringstream-buffer
|
|
*/
|
|
template<class...COD>
|
|
inline DotOut
|
|
digraph (COD ...parts)
|
|
{
|
|
DotOut script;
|
|
script.putLine (Code{"digraph {"});
|
|
script.put (parts...);
|
|
script.putLine (Code{"}"});
|
|
return script;
|
|
}
|
|
|
|
|
|
}} // namespace lib::dot_gen
|
|
#endif /*LIB_DOT_GEN_H*/
|