diff --git a/README.md b/README.md
index 3f7df45..1143cbf 100644
--- a/README.md
+++ b/README.md
@@ -11,5 +11,5 @@ PHP Graphviz
Graphviz's generation for PHP.
* [Documentation](https://alexandresalome.github.io/graphviz/)
-* [Changelog](CHANGELOG.md)
-* [Contributors](CONTRIBUTORS.md)
+* [Changelog](docs/changelog.md)
+* [Contributors](docs/contributors.md)
diff --git a/composer.json b/composer.json
index 40b418e..c8baaf8 100644
--- a/composer.json
+++ b/composer.json
@@ -34,6 +34,7 @@
"phpunit/phpunit": "^9.5",
"symfony/process": "^5.4|^6.1",
"symfony/finder": "^5.4|^6.1",
- "symfony/var-dumper": "^5.4|^6.1"
+ "symfony/var-dumper": "^5.4|^6.1",
+ "phpstan/phpstan-phpunit": "^1.2"
}
}
diff --git a/CHANGELOG.md b/docs/changelog.md
similarity index 78%
rename from CHANGELOG.md
rename to docs/changelog.md
index 57b7bb0..adb7256 100644
--- a/CHANGELOG.md
+++ b/docs/changelog.md
@@ -1,6 +1,13 @@
CHANGELOG
=========
+2.1.0 (In development)
+----------------------
+
+* [feature] Add a ``commentLine`` method on graph objects
+* [feature] Add a ``commentBlock`` method on graph objects
+* [bug] Fix method parameters typing for edges and attributes
+
2.0.0 (23/10/2022)
------------------
diff --git a/CONTRIBUTORS.md b/docs/contributors.md
similarity index 100%
rename from CONTRIBUTORS.md
rename to docs/contributors.md
diff --git a/docs/usage/comment.md b/docs/usage/comment.md
new file mode 100644
index 0000000..831dea8
--- /dev/null
+++ b/docs/usage/comment.md
@@ -0,0 +1,114 @@
+---
+title: Add comments
+---
+
+Adding comments
+===============
+
+
+ Available from the version 2.1
+
+
+Add comments into the graph
+---------------------------
+
+### Single line comments
+
+You can add a comment inside your graph by using the method ``commentLine``:
+
+```php
+$graph = new Graphviz\Digraph();
+$graph->commentLine('Empty graph');
+echo $graph->render();
+
+# digraph G {
+# // Empty graph
+# }
+```
+
+Note that if you pass multiple lines to this method, it will add multiple lines:
+
+```php
+$graph = new Graphviz\Digraph();
+$graph->commentLine("Line 1\nLine 2");
+echo $graph->render();
+
+# digraph G {
+# // Line 1
+# // Line 2
+# }
+```
+
+You can also remove the initial space prefix by passing ``false`` as a second
+argument:
+
+```php
+$graph = new Graphviz\Digraph();
+$graph->commentLine('-- ASCII MASTER --//', false);
+echo $graph->render();
+
+# digraph G {
+# //-- ASCII MASTER --//
+# }
+```
+
+And for C++ style arguments (``#`` instead of ``//``), pass ``true`` as a third
+argument:
+
+```php
+$graph = new Graphviz\Digraph();
+$graph->commentLine('C++ style', true, false);
+echo $graph->render();
+
+# digraph G {
+# # C++ style
+# }
+```
+
+### Block comments
+
+You can add block comments by using the ``commentBlock`` function:
+
+```php
+$graph = new Graphviz\Digraph();
+$graph->commentBlock('My block comment');
+echo $graph->render();
+
+# digraph G {
+# /*
+# * My block comment
+# */
+# }
+```
+
+Multiple lines is also supported:
+
+```php
+$graph = new Graphviz\Digraph();
+$graph->commentBlock("My block comment\non multiple lines");
+echo $graph->render();
+
+# digraph G {
+# /*
+# * My block comment
+# * on multiple lines
+# */
+# }
+```
+
+
+
+You can disable new lines and spacing and new line indent by providing ``false``
+as a second argument:
+
+```php
+$graph = new Graphviz\Digraph();
+$graph->commentBlock("** ASCII fan\nNew line\n**", false);
+echo $graph->render();
+
+# digraph G {
+# /*** ASCII fan
+# New line\n
+# ***/
+# }
+```
diff --git a/mkdocs.yml b/mkdocs.yml
index 1a7cbc8..50198e2 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -14,6 +14,7 @@ nav:
- usage/attributes.md
- usage/nodes.md
- usage/edges.md
+ - usage/comment.md
- usage/rendering.md
- Graphviz:
- graphviz/subgraph.md
@@ -21,3 +22,6 @@ nav:
- examples/basic.md
- examples/subgraph.md
- examples/table.md
+ - About:
+ - changelog.md
+ - contributors.md
diff --git a/phpstan.neon b/phpstan.neon
index 41fd901..a48431f 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,8 +1,10 @@
parameters:
- level: 6
+ level: 7
paths:
- src
- src_dev
- tests
bootstrapFiles:
- vendor/autoload.php
+includes:
+ - vendor/phpstan/phpstan-phpunit/extension.neon
diff --git a/src/AbstractGraph.php b/src/AbstractGraph.php
index e24b304..9695d40 100644
--- a/src/AbstractGraph.php
+++ b/src/AbstractGraph.php
@@ -131,10 +131,10 @@ public function set(string $name, string|RawText $value): self
/**
* Define attributes for node/edge/graph.
*
- * @param string $name Name of type
- * @param array|AttributeBag $attributes Attributes of the type
+ * @param string $name Name of type
+ * @param array $attributes Attributes of the type
*/
- public function attr(string $name, array|AttributeBag $attributes): self
+ public function attr(string $name, array $attributes): self
{
$this->instructions[] = new AttributeSet($name, $attributes);
@@ -154,12 +154,12 @@ public function subgraph(string $id): Subgraph
/**
* Created a new node on graph.
*
- * @param string $id Identifier of node
- * @param array|AttributeBag $attributes Attributes to set on node
+ * @param string $id Identifier of node
+ * @param array $attributes Attributes to set on node
*
* @return self Fluid-interface
*/
- public function node(string $id, array|AttributeBag $attributes = []): self
+ public function node(string $id, array $attributes = []): self
{
$this->instructions[] = new Node($this, $id, $attributes);
@@ -191,10 +191,10 @@ public function nodes(array $nodes): self
/**
* Created a new node on graph.
*
- * @param string $id Identifier of node
- * @param array|AttributeBag $attributes Attributes to set on node
+ * @param string $id Identifier of node
+ * @param array $attributes Attributes to set on node
*/
- public function beginNode(string $id, array|AttributeBag $attributes = []): Node
+ public function beginNode(string $id, array $attributes = []): Node
{
return $this->instructions[] = new Node($this, $id, $attributes);
}
@@ -202,12 +202,12 @@ public function beginNode(string $id, array|AttributeBag $attributes = []): Node
/**
* Created a new edge on graph.
*
- * @param array> $list List of edges
- * @param array|AttributeBag $attributes Attributes to set on edge
+ * @param array> $list List of edges
+ * @param array $attributes Attributes to set on edge
*
* @return self Fluid-interface
*/
- public function edge(array $list, array|AttributeBag $attributes = []): self
+ public function edge(array $list, array $attributes = []): self
{
$this->beginEdge($list, $attributes);
@@ -215,16 +215,70 @@ public function edge(array $list, array|AttributeBag $attributes = []): self
}
/**
- * Created a new edge on graph.
+ * Creates a new edge on graph.
*
- * @param array> $list List of edges
- * @param array|AttributeBag $attributes Attributes to set on edge
+ * @param array> $list List of edges
+ * @param array $attributes Attributes to set on edge
*/
- public function beginEdge(array $list, array|AttributeBag $attributes = []): Edge
+ public function beginEdge(array $list, array $attributes = []): Edge
{
return $this->instructions[] = new Edge($this, $list, $attributes);
}
+ /**
+ * Adds a new comment line to the graph (starting with //, or #).
+ *
+ * If you pass a multiline string to this method, it will append multiple
+ * comment lines.
+ *
+ * @param string $comment The comment to add
+ * @param bool $withSpace Adds a space at the beginning of the comment
+ * @param bool $cppStyle Indicates if it's a classic (//) or C++ style (#)
+ *
+ * @return $this Fluid interface
+ */
+ public function commentLine(string $comment, bool $withSpace = true, bool $cppStyle = false): self
+ {
+ $space = $withSpace ? ' ' : '';
+ $prefix = $cppStyle ? '#' : '//';
+ foreach (explode("\n", $comment) as $line) {
+ $this->instructions[] = new Comment($prefix.$space.$line);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a new comment block to the graph (starting with /*).
+ *
+ * If you pass a multiline string to this method, it will append multiple
+ * comment lines.
+ *
+ * @param string $comment The comment to add
+ * @param bool $withSpace Adds a space at the beginning of the comment
+ *
+ * @return $this Fluid interface
+ */
+ public function commentBlock(string $comment, bool $withSpace = true): self
+ {
+ $lines = explode("\n", $comment);
+ if ($withSpace) {
+ $lines = array_merge(
+ ['/*'],
+ array_map(fn ($line) => ' * '.$line, $lines),
+ [' */'],
+ );
+ } else {
+ $lines[0] = '/*'.$lines[0];
+ $last = count($lines) - 1;
+ $lines[$last] .= '*/';
+ }
+
+ $this->instructions[] = new Comment(implode("\n", $lines), $withSpace);
+
+ return $this;
+ }
+
/**
* Fluid-interface to access parent.
*/
diff --git a/src/Comment.php b/src/Comment.php
new file mode 100644
index 0000000..89ebb6f
--- /dev/null
+++ b/src/Comment.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Graphviz;
+
+/**
+ * Comment.
+ *
+ * @author Alexandre Salomé
+ */
+class Comment implements InstructionInterface
+{
+ /** @var string Content of the comment */
+ protected string $content;
+
+ /** @var bool Indent the comment on rendering */
+ protected bool $indented;
+
+ /**
+ * Creates a new comment.
+ *
+ * @param string $content Comment content, with delimiters
+ * @param bool $indented Indicates if the content must be rendered indented
+ */
+ public function __construct(string $content, bool $indented = true)
+ {
+ $this->content = $content;
+ $this->indented = $indented;
+ }
+
+ public function getContent(): string
+ {
+ return $this->content;
+ }
+
+ public function isIndented(): bool
+ {
+ return $this->indented;
+ }
+}
diff --git a/src/Edge.php b/src/Edge.php
index 4567aa0..b77ae24 100644
--- a/src/Edge.php
+++ b/src/Edge.php
@@ -24,7 +24,7 @@ class Edge implements InstructionInterface
/**
* List of element identifiers.
*
- * @var string[]
+ * @var array>
*/
protected array $list;
@@ -36,9 +36,9 @@ class Edge implements InstructionInterface
/**
* Creates an edge.
*
- * @param string[] $list List of edges
- * @param array $attributes Associative array of attributes
* @param AbstractGraph $parent Parent instruction
+ * @param array> $list List of edges
+ * @param array $attributes Associative array of attributes
*/
public function __construct(AbstractGraph $parent, array $list, array $attributes = [])
{
@@ -50,7 +50,7 @@ public function __construct(AbstractGraph $parent, array $list, array $attribute
/**
* Returns list of elements composing the edge.
*
- * @return string[]
+ * @return array>
*/
public function getPath(): array
{
diff --git a/src/Output/DotRenderer.php b/src/Output/DotRenderer.php
index da5b554..dcea48f 100644
--- a/src/Output/DotRenderer.php
+++ b/src/Output/DotRenderer.php
@@ -13,6 +13,7 @@
use Graphviz\Assign;
use Graphviz\AttributeBag;
use Graphviz\AttributeSet;
+use Graphviz\Comment;
use Graphviz\Digraph;
use Graphviz\Edge;
use Graphviz\Graph;
@@ -159,6 +160,18 @@ public function renderNode(Node $node, int $indent = 0): string
;
}
+ public function renderComment(Comment $comment, int $indent = 0): string
+ {
+ $prefix = str_repeat($this->indentSpacer, $indent);
+ $indentSpace = $comment->isIndented() ? $prefix : '';
+
+ return
+ $prefix.
+ str_replace("\n", "\n".$indentSpace, $comment->getContent()).
+ "\n"
+ ;
+ }
+
public function renderInstruction(InstructionInterface $instruction, int $indent = 0): string
{
if ($instruction instanceof Assign) {
@@ -177,6 +190,10 @@ public function renderInstruction(InstructionInterface $instruction, int $indent
return $this->renderEdge($instruction, $indent);
}
+ if ($instruction instanceof Comment) {
+ return $this->renderComment($instruction, $indent);
+ }
+
if ($instruction instanceof AbstractGraph) {
return $this->render($instruction, $indent);
}
diff --git a/src_dev/Documentation/ImageRenderer.php b/src_dev/Documentation/ImageRenderer.php
index 3635f2c..a8ef9c5 100644
--- a/src_dev/Documentation/ImageRenderer.php
+++ b/src_dev/Documentation/ImageRenderer.php
@@ -38,6 +38,9 @@ public function renderImages(string $file): void
$absolutePath = $this->documentationDirectory.'/'.$dirname.$relativePath;
// File content
$content = file_get_contents($file);
+ if (false === $content) {
+ throw new \RuntimeException(sprintf('Unable to read "%s".', $file));
+ }
$lines = explode("\n", $content);
echo "- $relative\n";
@@ -151,6 +154,9 @@ private function renderGraphCode(array $lines, string $absolutePath, string $rel
private function renderWithGraphviz(array $lines, string $absolutePngPath): void
{
$fileBase = tempnam(sys_get_temp_dir(), 'graphviz_');
+ if (false === $fileBase) {
+ throw new \RuntimeException(sprintf('Unable to create a temporary file in "%s".', sys_get_temp_dir()));
+ }
unlink($fileBase);
$phpFile = $fileBase.'.php';
diff --git a/tests/AbstractGraphTest.php b/tests/AbstractGraphTest.php
index ae7e5aa..a92a5b4 100644
--- a/tests/AbstractGraphTest.php
+++ b/tests/AbstractGraphTest.php
@@ -14,6 +14,7 @@
use Graphviz\AttributeSet;
use Graphviz\Edge;
use Graphviz\Node;
+use Graphviz\Subgraph;
use PHPUnit\Framework\TestCase;
/**
@@ -28,7 +29,13 @@ public function testGet(): void
->node('bar', ['label' => 'baz'])
;
- $this->assertSame('baz', $graph->get('foo')->get('bar')->getAttribute('label'));
+ $foo = $graph->get('foo');
+ $this->assertInstanceOf(Subgraph::class, $foo);
+
+ $bar = $foo->get('bar');
+ $this->assertInstanceOf(Node::class, $bar);
+
+ $this->assertSame('baz', $bar->getAttribute('label'));
}
public function testGetNotExisting(): void
@@ -89,23 +96,19 @@ public function testFluidInterfaceShort(): void
$this->assertCount(4, $instructions = $graph->getInstructions());
- /** @var Assign $assign */
$assign = $instructions[0];
$this->assertInstanceOf(Assign::class, $assign);
$this->assertSame('rankdir', $assign->getName());
$this->assertSame('LR', $assign->getValue());
- /** @var Node $node */
$node = $instructions[1];
$this->assertInstanceOf(Node::class, $node);
$this->assertSame('A', $node->getId());
- /** @var Node $node */
$node = $instructions[2];
$this->assertInstanceOf(Node::class, $node);
$this->assertSame('B', $node->getId());
- /** @var Edge $edge */
$edge = $instructions[3];
$this->assertInstanceOf(Edge::class, $edge);
$this->assertSame(['A', 'B'], $edge->getPath());
@@ -184,13 +187,11 @@ public function testFluidInterfaceVerbose(): void
$this->assertCount(2, $instructions = $graph->getInstructions());
- /** @var Node $node */
$node = $instructions[0];
$this->assertInstanceOf(Node::class, $node);
$this->assertSame('A', $node->getId());
$this->assertSame('red', $node->getAttributeBag()->get('color'));
- /** @var Edge $edge */
$edge = $instructions[1];
$this->assertInstanceOf(Edge::class, $edge);
$this->assertSame(['A', 'B'], $edge->getPath());
@@ -203,7 +204,6 @@ public function testAttr(): void
$graph->attr('node', ['color' => 'blue']);
$this->assertCount(1, $instructions = $graph->getInstructions());
- /** @var AttributeSet $attributeSet */
$attributeSet = $instructions[0];
$this->assertInstanceOf(AttributeSet::class, $attributeSet);
$this->assertSame('node', $attributeSet->getName());
@@ -242,7 +242,63 @@ public function testSubGraph(): void
$this->assertSame('foo', $subgraph->getId(), 'Subgraph identifier');
$this->assertSame($graph, $subgraph->end(), 'Subgraph end');
- $this->assertSame("subgraph foo {\n A -> B;\n}\n", $subgraph->render(), 'Subgraph rendering');
+ $this->assertSame("subgraph foo {\n A -> B;\n}\n", $subgraph->render());
+ }
+
+ public function testCommentLine(): void
+ {
+ $graph = new TestGraph();
+ $subgraph = $graph->subgraph('foo');
+ $subgraph->commentLine('Foo');
+ $this->assertSame("subgraph foo {\n // Foo\n}\n", $subgraph->render());
+ }
+
+ public function testCommentLineMultiple(): void
+ {
+ $graph = new TestGraph();
+ $subgraph = $graph->subgraph('foo');
+ $subgraph->commentLine("Foo\nBar");
+ $this->assertSame("subgraph foo {\n // Foo\n // Bar\n}\n", $subgraph->render());
+ }
+
+ public function testCommentLineNoSpace(): void
+ {
+ $graph = new TestGraph();
+ $subgraph = $graph->subgraph('foo');
+ $subgraph->commentLine('Foo', false);
+ $this->assertSame("subgraph foo {\n //Foo\n}\n", $subgraph->render());
+ }
+
+ public function testCommentLineCppStyle(): void
+ {
+ $graph = new TestGraph();
+ $subgraph = $graph->subgraph('foo');
+ $subgraph->commentLine('Foo', true, true);
+ $this->assertSame("subgraph foo {\n # Foo\n}\n", $subgraph->render());
+ }
+
+ public function testCommentBlock(): void
+ {
+ $graph = new TestGraph();
+ $subgraph = $graph->subgraph('foo');
+ $subgraph->commentBlock('Foo');
+ $this->assertSame("subgraph foo {\n /*\n * Foo\n */\n}\n", $subgraph->render());
+ }
+
+ public function testCommentBlockMultiline(): void
+ {
+ $graph = new TestGraph();
+ $subgraph = $graph->subgraph('foo');
+ $subgraph->commentBlock("Foo\nBar");
+ $this->assertSame("subgraph foo {\n /*\n * Foo\n * Bar\n */\n}\n", $subgraph->render());
+ }
+
+ public function testCommentBlockNoSpace(): void
+ {
+ $graph = new TestGraph();
+ $subgraph = $graph->subgraph('foo');
+ $subgraph->commentBlock("Foo\nBar", false);
+ $this->assertSame("subgraph foo {\n /*Foo\nBar*/\n}\n", $subgraph->render());
}
}
diff --git a/tests/CommentTest.php b/tests/CommentTest.php
new file mode 100644
index 0000000..a4a4826
--- /dev/null
+++ b/tests/CommentTest.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Graphviz\Tests;
+
+use Graphviz\Comment;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @covers \Graphviz\Comment
+ */
+class CommentTest extends TestCase
+{
+ public function testGetContent(): void
+ {
+ $comment = new Comment('// foo');
+ $this->assertSame('// foo', $comment->getContent());
+ }
+
+ public function testIsIndented(): void
+ {
+ $comment = new Comment('// foo');
+ $this->assertTrue($comment->isIndented());
+ $comment = new Comment('// foo', false);
+ $this->assertFalse($comment->isIndented());
+ }
+}
diff --git a/tests/Output/DotRendererTest.php b/tests/Output/DotRendererTest.php
index 0537669..c1a15d6 100644
--- a/tests/Output/DotRendererTest.php
+++ b/tests/Output/DotRendererTest.php
@@ -6,6 +6,7 @@
use Graphviz\Assign;
use Graphviz\AttributeBag;
use Graphviz\AttributeSet;
+use Graphviz\Comment;
use Graphviz\Digraph;
use Graphviz\Edge;
use Graphviz\Graph;
@@ -170,4 +171,17 @@ public function testRenderNode(): void
$node = new Node(new Graph(), 'A');
$this->assertSame("A;\n", $this->renderer->renderNode($node));
}
+
+ public function testRenderCommentLine(): void
+ {
+ $comment = new Comment('// Foo');
+ $this->assertSame("// Foo\n", $this->renderer->renderInstruction($comment));
+ }
+
+ public function testRenderCommentBlock(): void
+ {
+ $comment = new Comment("/*\n * Foo\n */");
+ $this->assertSame("/*\n * Foo\n */\n", $this->renderer->renderInstruction($comment));
+ $this->assertSame(" /*\n * Foo\n */\n", $this->renderer->renderInstruction($comment, 1));
+ }
}