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)); + } }