From d97f5b5b1f3370b5001d972cd1e93b11ee747d89 Mon Sep 17 00:00:00 2001 From: jhofm Date: Sat, 26 Dec 2020 22:53:11 +0100 Subject: [PATCH] plantuml output formats, runtime options refactored, output path arg --- .gitignore | 3 +- README.md | 85 +- bin/ph-puml | 1 + composer.json | 5 +- composer.lock | 1506 ------------------- config/services.yml | 48 + doc/img/ph-puml.svg | 290 ++-- doc/src/ph-puml.puml | 101 +- src/CodeProvider/CodeProvider.php | 27 +- src/Command/ClassDiagramCommand.php | 61 +- src/Formatter/Formatter.php | 81 + src/Formatter/FormatterException.php | 12 + src/Formatter/FormatterInterface.php | 13 + src/Formatter/NullFormatterStrategy.php | 16 + src/Formatter/PlantUmlFormatterStrategy.php | 84 ++ src/Options/Option.php | 59 + src/Options/OptionConfiguration.php | 18 + src/Options/OptionInterface.php | 19 + src/Options/Options.php | 160 +- src/Service/PhPuml.php | 6 +- 20 files changed, 882 insertions(+), 1713 deletions(-) delete mode 100644 composer.lock create mode 100644 src/Formatter/Formatter.php create mode 100644 src/Formatter/FormatterException.php create mode 100644 src/Formatter/FormatterInterface.php create mode 100644 src/Formatter/NullFormatterStrategy.php create mode 100644 src/Formatter/PlantUmlFormatterStrategy.php create mode 100644 src/Options/Option.php create mode 100644 src/Options/OptionConfiguration.php create mode 100644 src/Options/OptionInterface.php diff --git a/.gitignore b/.gitignore index fd954dc..9bfe39f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /vendor/ !/var/cache/.gitkeep /var/cache/* -/*.puml \ No newline at end of file +/*.puml +/composer.lock \ No newline at end of file diff --git a/README.md b/README.md index 5d32751..4715057 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,16 @@ ## About PhPuml -PhPuml is a console tool that creates PlantUML class diagram definitions (`*.puml`) from PHP code, written in PHP. +PhPuml generates PlantUML class diagrams from PHP code. Here's a class diagram of the tool, created by itself: ![PhPuml class diagram](./doc/img/ph-puml.svg) -And the [generated puml file](./doc/src/ph-puml.puml) the diagram is based on. - ## Features * Convenient installation via composer + * Generate PlantUml files without having PlantUML installed or generate all supported formats (png, svg, latex, etc) using a `plantuml.jar` executable * Packages from Namespaces * Generates inheritance relationships for classes, interfaces and traits * Generates class properties & method signatures, including type hints from @var doc comments @@ -27,59 +26,87 @@ And the [generated puml file](./doc/src/ph-puml.puml) the diagram is based on. ## Installation -The preferred way to install PhPuml is globally through composer. - -```console -$ composer global require jhofm/ph-puml -``` - -If you already have composer's global vendor/bin folder in your PATH, PhPuml can be executed by calling the script ```ph-puml```. -Otherwise the executable can be found at ``~/.composer/vendor/bin/ph-puml``. +The easiest way to install PhPuml is as a composer project. -Alternatively you can also install PhPuml as a composer project. - -```console +```bash $ composer create-project jhofm/ph-puml $ ./bin/ph-puml ``` -Or clone this repository and install the composer dependencies yourself. +It is also possible to install it globally. This may not work if other globally installed +packages have conflicting dependencies. -```console -$ git clone git@github.com:jhofm/ph-puml.git -$ cd ph-puml -$ composer install -$ ./bin/ph-puml -``` + ```bash + $ composer global require jhofm/ph-puml + ``` ## Quick Start -The `ph-puml` script will output PlantUML code describing all PHP files found in the current -folder. +The `ph-puml` script will output PlantUML syntax describing all PHP files found in the current folder when run without any parameters. ```bash $ cd mycode/src $ ph-puml ``` -Alternatively it accepts a relative or absolute path to a target directory or file as an argument. +You can specify a relative or absolute path to a target directory or file as the first argument. ```bash $ ph-puml mycode/src ``` -The resulting PlantUML code is written to the console's standard output and can of course be piped into a file instead. +The second optional argument is the output path. The console's standard output will be used if none is specified. + +The following two commands produce the same result: +```bash +$ ph-puml mycode/src > class.puml +$ ph-puml mycode/src class.puml +``` + +## Advanced features + +### Output formats + +PhPuml generates PlantUML puml file syntax by default, but you can also export most output formats supported by PlantUML directly. + +Currently, these are: + - eps (Postscript) + - latex (LaTeX/Tikz) + - latex:nopreamble (LaTeX/Tikz without preamble) + - png (PNG image) + - svg (SVG vector image) + - scxml (SCXML state chart, seems broken in PlantUML Version 1.2020.26) + - txt (ASCII art) + - utxt (ASCII art with unicode letters) + - vdx (VDX image) + - xmi (XMI metadata description) + +This requires a Java Runtime Environment on the machine running PhPuml. See the [PlantUML guide](https://plantuml.com/starting) for more information. +You also need to either: + +- provide a path to a `plantuml.jar` file + +```bash +$ ph-puml /my/code/dir --plantuml-path /somedir/plantuml.jar --format svg > ~/mycode.svg +``` + + - or install the optional [jawira/plantuml](https://packagist.org/packages/jawira/plantuml) package ```bash -$ ph-puml > class.puml +$ composer create-project jhofm/ph-puml +$ cd ph-puml +$ composer require jawira/plantuml +$ ph-puml /my/code/dir --format svg > ~/mycode.svg ``` -If the target path is a directory, PhPuml will determine the code files to analyze using a set of inclusion and exclusion rules. +### Path filters + +If the input path is a directory, PhPuml will determine the code files to analyze using a set of inclusion and exclusion rules. By default, files in the directory tree with the file extension `.php` are included, as long as none of their parent folders are called `vendor`. You can override the filter rules with command line options. All rules are regular expressions. You can use several at the same time. For example the following command will NOT skip files from `vendor` folders, and analyze files in the `includes` folder with the file extension `.inc` as well. -```console +```bash $ ph-puml --exclude --include "/\.php$/" --include "/^includes/.*\.inc$/" ``` @@ -87,7 +114,7 @@ The command will fail when attempting to parse files that do not contain valid P PhPuml uses `symfony/command`, so a help page including all supported arguments and options is available. -```console +```bash $ ph-puml -h ``` diff --git a/bin/ph-puml b/bin/ph-puml index 0190b6f..f3b793d 100755 --- a/bin/ph-puml +++ b/bin/ph-puml @@ -26,6 +26,7 @@ $container = (function (): ProjectServiceContainer { new FileLocator( dirname(__DIR__) . '/config')); $loader->load('services.yml'); + $containerBuilder->setParameter('root-dir', dirname(__DIR__)); $containerBuilder->compile(); file_put_contents($cachePath, (new PhpDumper($containerBuilder))->dump()); } diff --git a/composer.json b/composer.json index 75aa7d5..346f976 100644 --- a/composer.json +++ b/composer.json @@ -10,8 +10,6 @@ } ], "bin": ["bin/ph-puml"], - "require-dev": { - }, "require": { "ext-json": "*", "symfony/console": "^5.2", @@ -23,6 +21,9 @@ "symfony/config": "^5.2", "symfony/yaml": "^5.2" }, + "suggest": { + "jawira/plantuml": "For generating output using PlantUML" + }, "autoload": { "psr-4": { "Jhofm\\PhPuml\\": "src" diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 04da607..0000000 --- a/composer.lock +++ /dev/null @@ -1,1506 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "f5a3eb9ad9c056e79ac60ef14ed39a56", - "packages": [ - { - "name": "league/flysystem", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "88ab4780a5cc573fb943c8a6987da880b3ec0474" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/88ab4780a5cc573fb943c8a6987da880b3ec0474", - "reference": "88ab4780a5cc573fb943c8a6987da880b3ec0474", - "shasum": "" - }, - "require": { - "ext-json": "*", - "league/mime-type-detection": "^1.0.0", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "guzzlehttp/ringphp": "<1.1.1" - }, - "require-dev": { - "async-aws/s3": "^1.5", - "async-aws/simple-s3": "^1.0", - "aws/aws-sdk-php": "^3.132.4", - "composer/semver": "^3.0", - "ext-fileinfo": "*", - "friendsofphp/php-cs-fixer": "^2.16", - "google/cloud-storage": "^1.23", - "phpseclib/phpseclib": "^2.0", - "phpstan/phpstan": "^0.12.26", - "phpunit/phpunit": "^8.5 || ^9.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "League\\Flysystem\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frankdejonge.nl" - } - ], - "description": "File storage abstraction for PHP", - "keywords": [ - "WebDAV", - "aws", - "cloud", - "file", - "files", - "filesystem", - "filesystems", - "ftp", - "s3", - "sftp", - "storage" - ], - "support": { - "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/2.0.0" - }, - "funding": [ - { - "url": "https://offset.earth/frankdejonge", - "type": "custom" - }, - { - "url": "https://github.com/frankdejonge", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" - } - ], - "time": "2020-11-24T14:30:57+00:00" - }, - { - "name": "league/mime-type-detection", - "version": "1.5.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "353f66d7555d8a90781f6f5e7091932f9a4250aa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/353f66d7555d8a90781f6f5e7091932f9a4250aa", - "reference": "353f66d7555d8a90781f6f5e7091932f9a4250aa", - "shasum": "" - }, - "require": { - "ext-fileinfo": "*", - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.36", - "phpunit/phpunit": "^8.5.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "League\\MimeTypeDetection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frankdejonge.nl" - } - ], - "description": "Mime-type detection for Flysystem", - "support": { - "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.5.1" - }, - "funding": [ - { - "url": "https://github.com/frankdejonge", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" - } - ], - "time": "2020-10-18T11:50:25+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v4.10.4", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.0" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.9-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4" - }, - "time": "2020-12-20T10:01:03+00:00" - }, - { - "name": "psr/container", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/master" - }, - "time": "2017-02-14T16:28:37+00:00" - }, - { - "name": "symfony/config", - "version": "v5.2.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "d0a82d965296083fe463d655a3644cbe49cbaa80" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/d0a82d965296083fe463d655a3644cbe49cbaa80", - "reference": "d0a82d965296083fe463d655a3644cbe49cbaa80", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/filesystem": "^4.4|^5.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.15" - }, - "conflict": { - "symfony/finder": "<4.4" - }, - "require-dev": { - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/messenger": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/yaml": "^4.4|^5.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Config Component", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/config/tree/v5.2.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-12-09T18:54:12+00:00" - }, - { - "name": "symfony/console", - "version": "v5.2.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "47c02526c532fb381374dab26df05e7313978976" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/47c02526c532fb381374dab26df05e7313978976", - "reference": "47c02526c532fb381374dab26df05e7313978976", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1|^2", - "symfony/string": "^5.1" - }, - "conflict": { - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" - }, - "provide": { - "psr/log-implementation": "1.0" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Console Component", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v5.2.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-12-18T08:03:05+00:00" - }, - { - "name": "symfony/dependency-injection", - "version": "v5.2.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "7f8a9e9eff0581a33e20f6c5d41096fe22832d25" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/7f8a9e9eff0581a33e20f6c5d41096fe22832d25", - "reference": "7f8a9e9eff0581a33e20f6c5d41096fe22832d25", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.0", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1.6|^2" - }, - "conflict": { - "symfony/config": "<5.1", - "symfony/finder": "<4.4", - "symfony/proxy-manager-bridge": "<4.4", - "symfony/yaml": "<4.4" - }, - "provide": { - "psr/container-implementation": "1.0", - "symfony/service-implementation": "1.0" - }, - "require-dev": { - "symfony/config": "^5.1", - "symfony/expression-language": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony DependencyInjection Component", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v5.2.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-12-18T08:03:05+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665", - "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/master" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-09-07T11:33:47+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v5.2.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "fa8f8cab6b65e2d99a118e082935344c5ba8c60d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/fa8f8cab6b65e2d99a118e082935344c5ba8c60d", - "reference": "fa8f8cab6b65e2d99a118e082935344c5ba8c60d", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Filesystem Component", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.2.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-11-30T17:05:38+00:00" - }, - { - "name": "symfony/finder", - "version": "v5.2.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "0b9231a5922fd7287ba5b411893c0ecd2733e5ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/0b9231a5922fd7287ba5b411893c0ecd2733e5ba", - "reference": "0b9231a5922fd7287ba5b411893c0ecd2733e5ba", - "shasum": "" - }, - "require": { - "php": ">=7.2.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Finder Component", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v5.2.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-12-08T17:02:38+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.20.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "f4ba089a5b6366e453971d3aad5fe8e897b37f41" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f4ba089a5b6366e453971d3aad5fe8e897b37f41", - "reference": "f4ba089a5b6366e453971d3aad5fe8e897b37f41", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.20-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.20.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-23T14:02:19+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.20.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c", - "reference": "c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.20-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.20.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-23T14:02:19+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.20.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "727d1096295d807c309fb01a851577302394c897" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/727d1096295d807c309fb01a851577302394c897", - "reference": "727d1096295d807c309fb01a851577302394c897", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.20-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.20.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-23T14:02:19+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.20.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "39d483bdf39be819deabf04ec872eb0b2410b531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/39d483bdf39be819deabf04ec872eb0b2410b531", - "reference": "39d483bdf39be819deabf04ec872eb0b2410b531", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.20-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.20.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-23T14:02:19+00:00" - }, - { - "name": "symfony/polyfill-php73", - "version": "v1.20.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "8ff431c517be11c78c48a39a66d37431e26a6bed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/8ff431c517be11c78c48a39a66d37431e26a6bed", - "reference": "8ff431c517be11c78c48a39a66d37431e26a6bed", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.20-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.20.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-23T14:02:19+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.20.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/e70aa8b064c5b72d3df2abd5ab1e90464ad009de", - "reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.20-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.20.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-23T14:02:19+00:00" - }, - { - "name": "symfony/process", - "version": "v5.2.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "bd8815b8b6705298beaa384f04fabd459c10bedd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/bd8815b8b6705298beaa384f04fabd459c10bedd", - "reference": "bd8815b8b6705298beaa384f04fabd459c10bedd", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Process Component", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v5.2.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-12-08T17:03:37+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", - "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.0" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/master" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-09-07T11:33:47+00:00" - }, - { - "name": "symfony/string", - "version": "v5.2.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", - "reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" - }, - "require-dev": { - "symfony/error-handler": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "files": [ - "Resources/functions.php" - ], - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony String component", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v5.2.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-12-05T07:33:16+00:00" - }, - { - "name": "symfony/yaml", - "version": "v5.2.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "290ea5e03b8cf9b42c783163123f54441fb06939" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/290ea5e03b8cf9b42c783163123f54441fb06939", - "reference": "290ea5e03b8cf9b42c783163123f54441fb06939", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/console": "<4.4" - }, - "require-dev": { - "symfony/console": "^4.4|^5.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v5.2.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-12-08T17:02:38+00:00" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "ext-json": "*" - }, - "platform-dev": [], - "plugin-api-version": "2.0.0" -} diff --git a/config/services.yml b/config/services.yml index e90b7bf..6cbd4cc 100644 --- a/config/services.yml +++ b/config/services.yml @@ -1,4 +1,9 @@ +parameters: + plantuml.executable: vendor\jawira\plantuml\bin\plantuml.jar services: + _instanceof: + Jhofm\PhPuml\Formatter\FormatterInterface: + tags: ['formatter'] _defaults: autowire: true @@ -19,3 +24,46 @@ services: Jhofm\PhPuml\: resource: '../src/*' + + Jhofm\PhPuml\Options\Options: + arguments: + - exclude: + name-short: e + is-array: true + value: ['~(?:^|[\\/])vendor[\\/]~'] + description: include regex patterns (at least one must match file path) + include: + name-short: i + is-array: true + value: ['/\.php$/'] + description: exclude regex patterns (none may match file path) + format: + name-short: f + is-array: false + value: puml + description: diagram output format. all formats apart from puml (the default) require the package jawira/plantuml to be present. + values: + - puml + - latex + - latex:nopreamble + - eps + - png + - scxml + - svg + - txt + - utxt + - vdx + - xmi + plantuml-path: + name-short: p + is-array: false + value: '%plantuml.executable%' + description: path to a local plantuml.jar executable. this can be used to output PlantUML output formats without installing the jawira/plantuml package. + Jhofm\PhPuml\Formatter\Formatter: + arguments: + - '@Jhofm\PhPuml\Options\Options' + - !tagged_iterator formatter + Jhofm\PhPuml\Formatter\PlantUmlFormatterStrategy: + arguments: + - '%root-dir%' + - '@Jhofm\PhPuml\Options\Options' \ No newline at end of file diff --git a/doc/img/ph-puml.svg b/doc/img/ph-puml.svg index da427ad..ec098ac 100644 --- a/doc/img/ph-puml.svg +++ b/doc/img/ph-puml.svg @@ -1,89 +1,120 @@ -Jhofm\PhPuml\CodeProviderLeague\FlysystemLeague\Flysystem\LocalJhofm\PhPumlJhofm\PhPuml\CommandSymfony\Component\Console\CommandJhofm\PhPuml\ServiceJhofm\PhPuml\OptionsJhofm\PhPuml\NodeVisitorPhpParser\NodeVisitorPhpParser\NodePhpParserPhpParser\CommentJhofm\PhPuml\RelationJhofm\PhPuml\RendererCodeProvidergetCode(directory:string, options:\Jhofm\PhPuml\Options\Options):\GeneratorgetIterator(directory:string, options:\Jhofm\PhPuml\Options\Options):\TraversableCodeProviderExceptionFilesystemLocalFilesystemAdapterPhPumlExceptionClassDiagramCommandphpumlService:Jhofm\PhPuml\Service\PhPuml__construct(phpumlService:\Jhofm\PhPuml\Service\PhPuml, name:string = null)configure()execute(input:\Symfony\Component\Console\Input\InputInterface, output:\Symfony\Component\Console\Output\OutputInterface)CommandPhPumlparser:PhpParser\ParsernamespaceTraverser:PhpParser\NodeTraversercodeProvider:Jhofm\PhPuml\CodeProvider\CodeProvidernodeFinder:PhpParser\NodeFinderclassLikeRenderer:Jhofm\PhPuml\Renderer\ClassLikeRendererrelationInferrer:Jhofm\PhPuml\Relation\RelationInferrerrelationRenderer:Jhofm\PhPuml\Renderer\RelationRenderer__construct(codeProvider:\Jhofm\PhPuml\CodeProvider\CodeProvider, nodeFinder:\PhpParser\NodeFinder, namespaceTraverser:\PhpParser\NodeTraverser, parser:\PhpParser\Parser, relationInferrer:\Jhofm\PhPuml\Relation\RelationInferrer, classLikeRenderer:\Jhofm\PhPuml\Renderer\ClassLikeRenderer, relationRenderer:\Jhofm\PhPuml\Renderer\RelationRenderer)generatePuml(input:string, options:\Jhofm\PhPuml\Options\Options):string«leaf»Optionsdefaults:arrayflags:mixedoptions:arrayfromArray(options:array):\Jhofm\PhPuml\Options\OptionsgetDefaults():arraygetFlags(option:string):int = null__construct(options:array)__get(name:string)jsonSerialize()OptionsExceptionNameResolver__construct()enterNode(node:\PhpParser\Node)addAlias(use:\PhpParser\Node\Stmt\UseUse, type:mixed, prefix:\PhpParser\Node\Name)resolveSignature(node:mixed)resolveType(node:mixed)resolveAttributes(node:\PhpParser\Node\Stmt\Property)NameResolverNameBuilderHelpersNodeNodeFinderNodeTraverserParserDocRelationsource:PhpParser\Nodetarget:PhpParser\NoderelationType:stringrole:Jhofm\PhPuml\Relation\string|nullsourceQuantifier:Jhofm\PhPuml\Relation\int|nulltargetQuantifier:Jhofm\PhPuml\Relation\int|null__construct(source:\PhpParser\Node, target:\PhpParser\Node, relationType:string, role:string = null, sourceQuantifier:int = null, targetQuantifier:int = null)getRelationType():stringgetSource():\PhpParser\NodegetTarget():\PhpParser\NodegetRole():string = nullgetSourceQuantifier():int = nullgetTargetQuantifier():int = nullRelationInferrernodeFinder:PhpParser\NodeFindertypeRenderer:Jhofm\PhPuml\Renderer\TypeRenderer__construct(nodeFinder:\PhpParser\NodeFinder, typeRenderer:\Jhofm\PhPuml\Renderer\TypeRenderer)inferRelations(node:\PhpParser\Node\Stmt\ClassLike):arraygetConstructorArgumentTypes(node:\PhpParser\Node):arraygetTypesFromNodeTypes(node:\PhpParser\Node\Stmt\ClassLike, types:array):arraygetTypeFromNode(nodeOfType:\PhpParser\Node):\PhpParser\Node\Name = nullTypeRendererrender(type:\PhpParser\Node = null):stringClassLikeRenderertypeMap:arraytypeRenderer:Jhofm\PhPuml\Renderer\TypeRenderer__construct(typeRenderer:\Jhofm\PhPuml\Renderer\TypeRenderer)render(node:\PhpParser\Node\Stmt\ClassLike):stringrenderClassLikeHeader(node:\PhpParser\Node\Stmt\ClassLike):stringrenderProperties(node:\PhpParser\Node\Stmt\ClassLike):stringrenderProperty(property:\PhpParser\Node\Stmt\Property):stringrenderMethods(node:\PhpParser\Node\Stmt\ClassLike):stringrenderMethod(method:\PhpParser\Node\Stmt\ClassMethod):stringrenderVisibility(node:\PhpParser\Node\Stmt):stringrenderExtends(node:\PhpParser\Node\Stmt\ClassLike):string«trait»IndentedRenderTraitindentation:intindentationString:stringappendLine(puml:string, line:string):voidRendererExceptionRelationRenderertypeRenderer:Jhofm\PhPuml\Renderer\TypeRenderer__construct(typeRenderer:\Jhofm\PhPuml\Renderer\TypeRenderer)renderRelations(relations:array):stringrender(relation:\Jhofm\PhPuml\Relation\Relation):stringrenderRelationType(relation:\Jhofm\PhPuml\Relation\Relation, sourceQuantifier:int = null, targetQuantifier:int = null):stringrenderQuantifier(sourceQuantifier:int = null):stringJsonSerializableExceptionthrowscreatescreatesusesusesusescreatesthrowscreatesthrowsthrowsJhofm\PhPuml\CodeProviderJhofm\PhPuml\OptionsLeague\FlysystemLeague\Flysystem\LocalJhofm\PhPumlJhofm\PhPuml\CommandSymfony\Component\Console\CommandJhofm\PhPuml\ServiceJhofm\PhPuml\FormatterSymfony\Component\ProcessJhofm\PhPuml\NodeVisitorPhpParser\NodeVisitorPhpParser\NodePhpParserPhpParser\CommentJhofm\PhPuml\RelationJhofm\PhPuml\RendererCodeProvideroptions:mixed__construct(options:\Jhofm\PhPuml\Options\Options)getCode(directory:string):\GeneratorgetIterator(directory:string):\TraversableCodeProviderException«leaf»Optionsoptions:array__construct(options:array)validateConfig(options:array)getIterator()setValues(values:array):selfhas(name:string):bool__get(name:string)get(name:string)getOption(name:string):\Jhofm\PhPuml\Options\OptionInterface__set(name:string, value:mixed):selfset(name:string, value:mixed):selfjsonSerialize()validate(name:string, value:mixed)«leaf»Option__construct(config:array)isArray():boolgetValue()__toString():stringgetValidValues():array = nullgetName():stringgetShortName():string = nullgetDescription():string = nullOptionInterfacegetName():stringgetShortName():string = nullgetDescription():string = nullgetValidValues():array = nullgetValue()isArray():bool__toString():string«leaf»OptionConfigurationOptionsExceptionFilesystemLocalFilesystemAdapterPhPumlExceptionClassDiagramCommandphpumlService:Jhofm\PhPuml\Service\PhPumloptions:Jhofm\PhPuml\Options\Optionsformatter:Jhofm\PhPuml\Formatter\Formatter__construct(phpumlService:\Jhofm\PhPuml\Service\PhPuml, options:\Jhofm\PhPuml\Options\Options, formatter:\Jhofm\PhPuml\Formatter\Formatter, name:string = null)configure()execute(input:\Symfony\Component\Console\Input\InputInterface, output:\Symfony\Component\Console\Output\OutputInterface)CommandPhPumlparser:PhpParser\ParsernamespaceTraverser:PhpParser\NodeTraversercodeProvider:Jhofm\PhPuml\CodeProvider\CodeProvidernodeFinder:PhpParser\NodeFinderclassLikeRenderer:Jhofm\PhPuml\Renderer\ClassLikeRendererrelationInferrer:Jhofm\PhPuml\Relation\RelationInferrerrelationRenderer:Jhofm\PhPuml\Renderer\RelationRenderer__construct(codeProvider:\Jhofm\PhPuml\CodeProvider\CodeProvider, nodeFinder:\PhpParser\NodeFinder, namespaceTraverser:\PhpParser\NodeTraverser, parser:\PhpParser\Parser, relationInferrer:\Jhofm\PhPuml\Relation\RelationInferrer, classLikeRenderer:\Jhofm\PhPuml\Renderer\ClassLikeRenderer, relationRenderer:\Jhofm\PhPuml\Renderer\RelationRenderer)generatePuml(input:string):stringFormatterformatters:array<Jhofm\PhPuml\Formatter\FormatterInterface>options:Jhofm\PhPuml\Options\Options__construct(options:\Jhofm\PhPuml\Options\Options, formatters:mixed)format(puml:string = null):stringgetFormatterByFormat(format:string):\Jhofm\PhPuml\Formatter\FormatterInterfacegetFormatterByClassname(class:string):\Jhofm\PhPuml\Formatter\FormatterInterfaceFormatterInterfaceformat(puml:string):stringFormatterExceptionNullFormatterStrategyformat(puml:string):stringPlantUmlFormatterStrategyoptions:Jhofm\PhPuml\Options\OptionsrootDir:string__construct(rootDir:string, options:\Jhofm\PhPuml\Options\Options)format(puml:string):stringgetPlantUmlParameterForFormat(format:string):stringgetPlantUmlJarPath():stringProcessNameResolver__construct()enterNode(node:\PhpParser\Node)addAlias(use:\PhpParser\Node\Stmt\UseUse, type:mixed, prefix:\PhpParser\Node\Name)resolveSignature(node:mixed)resolveType(node:mixed)resolveAttributes(node:\PhpParser\Node\Stmt\Property)NameResolverNameBuilderHelpersNodeNodeFinderNodeTraverserParserDocRelationsource:PhpParser\Nodetarget:PhpParser\NoderelationType:stringrole:Jhofm\PhPuml\Relation\string|nullsourceQuantifier:Jhofm\PhPuml\Relation\int|nulltargetQuantifier:Jhofm\PhPuml\Relation\int|null__construct(source:\PhpParser\Node, target:\PhpParser\Node, relationType:string, role:string = null, sourceQuantifier:int = null, targetQuantifier:int = null)getRelationType():stringgetSource():\PhpParser\NodegetTarget():\PhpParser\NodegetRole():string = nullgetSourceQuantifier():int = nullgetTargetQuantifier():int = nullRelationInferrernodeFinder:PhpParser\NodeFindertypeRenderer:Jhofm\PhPuml\Renderer\TypeRenderer__construct(nodeFinder:\PhpParser\NodeFinder, typeRenderer:\Jhofm\PhPuml\Renderer\TypeRenderer)inferRelations(node:\PhpParser\Node\Stmt\ClassLike):arraygetConstructorArgumentTypes(node:\PhpParser\Node):arraygetTypesFromNodeTypes(node:\PhpParser\Node\Stmt\ClassLike, types:array):arraygetTypeFromNode(nodeOfType:\PhpParser\Node):\PhpParser\Node\Name = nullTypeRendererrender(type:\PhpParser\Node = null):stringClassLikeRenderertypeMap:arraytypeRenderer:Jhofm\PhPuml\Renderer\TypeRenderer__construct(typeRenderer:\Jhofm\PhPuml\Renderer\TypeRenderer)render(node:\PhpParser\Node\Stmt\ClassLike):stringrenderClassLikeHeader(node:\PhpParser\Node\Stmt\ClassLike):stringrenderProperties(node:\PhpParser\Node\Stmt\ClassLike):stringrenderProperty(property:\PhpParser\Node\Stmt\Property):stringrenderMethods(node:\PhpParser\Node\Stmt\ClassLike):stringrenderMethod(method:\PhpParser\Node\Stmt\ClassMethod):stringrenderVisibility(node:\PhpParser\Node\Stmt):stringrenderExtends(node:\PhpParser\Node\Stmt\ClassLike):string«trait»IndentedRenderTraitindentation:intindentationString:stringappendLine(puml:string, line:string):voidRendererExceptionRelationRenderertypeRenderer:Jhofm\PhPuml\Renderer\TypeRenderer__construct(typeRenderer:\Jhofm\PhPuml\Renderer\TypeRenderer)renderRelations(relations:array):stringrender(relation:\Jhofm\PhPuml\Relation\Relation):stringrenderRelationType(relation:\Jhofm\PhPuml\Relation\Relation, sourceQuantifier:int = null, targetQuantifier:int = null):stringrenderQuantifier(sourceQuantifier:int = null):stringJsonSerializableIteratorAggregateExceptionthrowscreatescreatesthrowsthrowsthrowscreatesusesusescreatesthrowscreatescreatesthrowsthrows \\Jhofm\\PhPuml\\Options\\Options Jhofm\\PhPuml\\CodeProvider\\CodeProvider ..> \\Jhofm\\PhPuml\\CodeProvider\\CodeProviderException : throws Jhofm\\PhPuml\\CodeProvider\\CodeProvider ..> \\League\\Flysystem\\Filesystem : creates Jhofm\\PhPuml\\CodeProvider\\CodeProvider ..> \\League\\Flysystem\\Local\\LocalFilesystemAdapter : creates @@ -16,14 +20,56 @@ class Jhofm\\PhPuml\\CodeProvider\\CodeProviderException extends \\Jhofm\\PhPuml class Jhofm\\PhPuml\\Command\\ClassDiagramCommand extends \\Symfony\\Component\\Console\\Command\\Command { -phpumlService:Jhofm\PhPuml\Service\PhPuml + -options:Jhofm\PhPuml\Options\Options + -formatter:Jhofm\PhPuml\Formatter\Formatter - +__construct(phpumlService:\\Jhofm\\PhPuml\\Service\\PhPuml, name:string = null) + +__construct(phpumlService:\\Jhofm\\PhPuml\\Service\\PhPuml, options:\\Jhofm\\PhPuml\\Options\\Options, formatter:\\Jhofm\\PhPuml\\Formatter\\Formatter, name:string = null) +configure() +execute(input:\\Symfony\\Component\\Console\\Input\\InputInterface, output:\\Symfony\\Component\\Console\\Output\\OutputInterface) } Jhofm\\PhPuml\\Command\\ClassDiagramCommand --> \\Jhofm\\PhPuml\\Service\\PhPuml -Jhofm\\PhPuml\\Command\\ClassDiagramCommand ..> \\Jhofm\\PhPuml\\Options\\Options : uses +Jhofm\\PhPuml\\Command\\ClassDiagramCommand --> \\Jhofm\\PhPuml\\Options\\Options +Jhofm\\PhPuml\\Command\\ClassDiagramCommand --> \\Jhofm\\PhPuml\\Formatter\\Formatter +Jhofm\\PhPuml\\Command\\ClassDiagramCommand ..> \\Jhofm\\PhPuml\\PhPumlException : throws + +class Jhofm\\PhPuml\\Formatter\\Formatter implements \\Jhofm\\PhPuml\\Formatter\\FormatterInterface { + -formatters:array + -options:Jhofm\PhPuml\Options\Options + + +__construct(options:\\Jhofm\\PhPuml\\Options\\Options, formatters:mixed) + +format(puml:string = null):string + -getFormatterByFormat(format:string):\\Jhofm\\PhPuml\\Formatter\\FormatterInterface + -getFormatterByClassname(class:string):\\Jhofm\\PhPuml\\Formatter\\FormatterInterface +} + +Jhofm\\PhPuml\\Formatter\\Formatter --> \\Jhofm\\PhPuml\\Options\\Options +Jhofm\\PhPuml\\Formatter\\Formatter ..> \\Jhofm\\PhPuml\\Formatter\\FormatterException : throws + +class Jhofm\\PhPuml\\Formatter\\FormatterException extends \\Jhofm\\PhPuml\\PhPumlException { +} + +interface Jhofm\\PhPuml\\Formatter\\FormatterInterface { + +format(puml:string):string +} + +class Jhofm\\PhPuml\\Formatter\\NullFormatterStrategy implements \\Jhofm\\PhPuml\\Formatter\\FormatterInterface { + +format(puml:string):string +} + +class Jhofm\\PhPuml\\Formatter\\PlantUmlFormatterStrategy implements \\Jhofm\\PhPuml\\Formatter\\FormatterInterface { + -options:Jhofm\PhPuml\Options\Options + -rootDir:string + + +__construct(rootDir:string, options:\\Jhofm\\PhPuml\\Options\\Options) + +format(puml:string):string + -getPlantUmlParameterForFormat(format:string):string + -getPlantUmlJarPath():string +} + +Jhofm\\PhPuml\\Formatter\\PlantUmlFormatterStrategy --> \\Jhofm\\PhPuml\\Options\\Options +Jhofm\\PhPuml\\Formatter\\PlantUmlFormatterStrategy ..> \\Jhofm\\PhPuml\\Formatter\\FormatterException : throws +Jhofm\\PhPuml\\Formatter\\PlantUmlFormatterStrategy ..> \\Symfony\\Component\\Process\\Process : creates class Jhofm\\PhPuml\\NodeVisitor\\NameResolver extends \\PhpParser\\NodeVisitor\\NameResolver { +__construct() @@ -38,20 +84,49 @@ Jhofm\\PhPuml\\NodeVisitor\\NameResolver ..> \\PhpParser\\Node\\Name : uses Jhofm\\PhPuml\\NodeVisitor\\NameResolver ..> \\PhpParser\\BuilderHelpers : uses Jhofm\\PhPuml\\NodeVisitor\\NameResolver ..> \\PhpParser\\Comment\\Doc : creates -class Jhofm\\PhPuml\\Options\\Options <> implements \\JsonSerializable { - {static} -defaults:array - {static} -flags:mixed - #options:array +class Jhofm\\PhPuml\\Options\\Option <> implements \\Jhofm\\PhPuml\\Options\\OptionInterface { + +__construct(config:array) + +isArray():bool + +getValue() + +__toString():string + +getValidValues():array = null + +getName():string + +getShortName():string = null + +getDescription():string = null +} + +class Jhofm\\PhPuml\\Options\\OptionConfiguration <> { +} + +interface Jhofm\\PhPuml\\Options\\OptionInterface { + +getName():string + +getShortName():string = null + +getDescription():string = null + +getValidValues():array = null + +getValue() + +isArray():bool + +__toString():string +} + +class Jhofm\\PhPuml\\Options\\Options <> implements \\JsonSerializable, \\IteratorAggregate { + -options:array - {static} +fromArray(options:array):\\Jhofm\\PhPuml\\Options\\Options - {static} +getDefaults():array - {static} +getFlags(option:string):int = null - -__construct(options:array) + +__construct(options:array) + -validateConfig(options:array) + +getIterator() + +setValues(values:array):self + +has(name:string):bool +__get(name:string) + +get(name:string) + +getOption(name:string):\\Jhofm\\PhPuml\\Options\\OptionInterface + +__set(name:string, value:mixed):self + +set(name:string, value:mixed):self +jsonSerialize() + -validate(name:string, value:mixed) } Jhofm\\PhPuml\\Options\\Options ..> \\Jhofm\\PhPuml\\Options\\OptionsException : throws +Jhofm\\PhPuml\\Options\\Options ..> \\Jhofm\\PhPuml\\Options\\Option : creates class Jhofm\\PhPuml\\Options\\OptionsException extends \\Jhofm\\PhPuml\\PhPumlException { } @@ -147,7 +222,7 @@ class Jhofm\\PhPuml\\Service\\PhPuml { -relationRenderer:Jhofm\PhPuml\Renderer\RelationRenderer +__construct(codeProvider:\\Jhofm\\PhPuml\\CodeProvider\\CodeProvider, nodeFinder:\\PhpParser\\NodeFinder, namespaceTraverser:\\PhpParser\\NodeTraverser, parser:\\PhpParser\\Parser, relationInferrer:\\Jhofm\\PhPuml\\Relation\\RelationInferrer, classLikeRenderer:\\Jhofm\\PhPuml\\Renderer\\ClassLikeRenderer, relationRenderer:\\Jhofm\\PhPuml\\Renderer\\RelationRenderer) - +generatePuml(input:string, options:\\Jhofm\\PhPuml\\Options\\Options):string + +generatePuml(input:string):string } Jhofm\\PhPuml\\Service\\PhPuml --> \\Jhofm\\PhPuml\\CodeProvider\\CodeProvider diff --git a/src/CodeProvider/CodeProvider.php b/src/CodeProvider/CodeProvider.php index 88fd152..6851daa 100644 --- a/src/CodeProvider/CodeProvider.php +++ b/src/CodeProvider/CodeProvider.php @@ -19,6 +19,17 @@ class CodeProvider { private const RESULT_KEY_PATH = 'path'; private const RESULT_KEY_CONTENT = 'content'; + private $options; + + /** + * CodeProvider constructor. + * + * @param Options $options + */ + public function __construct(Options $options) + { + $this->options = $options; + } /** * @param string $directory @@ -26,7 +37,7 @@ class CodeProvider * @return Generator * @throws CodeProviderException */ - public function getCode(string $directory, Options $options): Generator { + public function getCode(string $directory): Generator { $directory = realpath($directory); if ($directory === false) { @@ -35,7 +46,7 @@ public function getCode(string $directory, Options $options): Generator { if (is_dir($directory)) { try { - $iterator = $this->getIterator($directory, $options); + $iterator = $this->getIterator($directory); foreach ($iterator as $file) { yield $file[self::RESULT_KEY_PATH] => $file[self::RESULT_KEY_CONTENT]; } @@ -49,12 +60,11 @@ public function getCode(string $directory, Options $options): Generator { /** * @param string $directory - * @param Options $options * * @return Traversable * @throws FilesystemException */ - private function getIterator(string $directory, Options $options): Traversable + private function getIterator(string $directory): Traversable { $fs = new Filesystem( new LocalFilesystemAdapter( @@ -66,13 +76,13 @@ private function getIterator(string $directory, Options $options): Traversable ); return $fs->listContents('.', true) ->filter( - function (StorageAttributes $attributes) use ($options) { + function (StorageAttributes $attributes) { if (!$attributes->isFile()) { return false; } // at least one inclusion rule must match $match = false; - foreach ($options->{Options::OPTION_INCLUDE} as $includeRegex) { + foreach ($this->options->include as $includeRegex) { if ($includeRegex !== null && preg_match($includeRegex, $attributes->path())) { $match = true; break; @@ -80,7 +90,7 @@ function (StorageAttributes $attributes) use ($options) { } // no exclusion rule may match if ($match === true) { - foreach ($options->{Options::OPTION_EXCLUDE} as $excludeRegex) { + foreach ($this->options->exclude as $excludeRegex) { if ($excludeRegex !== null && preg_match($excludeRegex, $attributes->path())) { return false; } @@ -90,7 +100,7 @@ function (StorageAttributes $attributes) use ($options) { return false; } )->map( - function (StorageAttributes $attributes) use ($options, $directory, $fs) { + function (StorageAttributes $attributes) use ($directory, $fs) { return [ self::RESULT_KEY_PATH => $directory . '/' . $attributes->path(), self::RESULT_KEY_CONTENT => $fs->read($attributes->path()) @@ -98,5 +108,4 @@ function (StorageAttributes $attributes) use ($options, $directory, $fs) { } ); } - } diff --git a/src/Command/ClassDiagramCommand.php b/src/Command/ClassDiagramCommand.php index c8213f5..d92f435 100644 --- a/src/Command/ClassDiagramCommand.php +++ b/src/Command/ClassDiagramCommand.php @@ -4,12 +4,16 @@ namespace Jhofm\PhPuml\Command; -use Jhofm\PhPuml\Exception\PhPumlException; +use Jhofm\PhPuml\Formatter\Formatter; +use Jhofm\PhPuml\Options\OptionInterface; use Jhofm\PhPuml\Options\Options; +use Jhofm\PhPuml\PhPumlException; use Jhofm\PhPuml\Service\PhPuml; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\Output; use Symfony\Component\Console\Output\OutputInterface; /** @@ -17,23 +21,34 @@ */ class ClassDiagramCommand extends Command { - private const ARG_INPUT_PATH_OR_PACKAGE = 'input'; + private const ARG_INPUT_PATH = 'input'; + private const ARG_OUTPUT_PATH = 'output'; /** @var PhPuml */ private $phpumlService; + /** @var Options */ + private $options; + /** @var Formatter */ + private $formatter; /** * PumlGenCommand constructor. * * @param PhPuml $phpumlService + * @param Options $options + * @param Formatter $formatter * @param string|null $name */ public function __construct( PhPuml $phpumlService, + Options $options, + Formatter $formatter, ?string $name = null ) { - parent::__construct($name); $this->phpumlService = $phpumlService; + $this->options = $options; + $this->formatter = $formatter; + parent::__construct($name); } /** @@ -44,14 +59,27 @@ public function configure() $this->setName('ph-puml'); $this->setDescription('Generates PlantUML class diagrams from PHP code'); $this->addArgument( - self::ARG_INPUT_PATH_OR_PACKAGE, + self::ARG_INPUT_PATH, InputArgument::OPTIONAL, - 'directory path containing PHP code (absolute or relative)', + 'Directory path containing PHP code (absolute or relative)', '.' ); - $pumlOptions = Options::getDefaults(); - foreach ($pumlOptions as $option => $defaultValue) { - $this->addOption($option, null, Options::getFlags($option), '', $defaultValue); + $this->addArgument( + self::ARG_OUTPUT_PATH, + InputArgument::OPTIONAL, + 'Output path (absolute or relative)', + 'php://stdout' + ); + /** + * @var string $name + * @var OptionInterface $option + */ + foreach ($this->options as $name => $option) { + $mode = InputOption::VALUE_OPTIONAL; + if ($option->isArray()) { + $mode |= InputOption::VALUE_IS_ARRAY; + } + $this->addOption($name, $option->getShortName(), $mode, $option->getDescription(), $option->getValue()); } } @@ -64,12 +92,17 @@ public function configure() */ public function execute(InputInterface $input, OutputInterface $output) { - $options = Options::fromArray($input->getOptions()); - $puml = $this->phpumlService->generatePuml( - $input->getArgument(self::ARG_INPUT_PATH_OR_PACKAGE), - $options - ); - $output->write($puml); + $this->options->setValues($input->getOptions()); + $puml = $this->phpumlService->generatePuml($input->getArgument(self::ARG_INPUT_PATH)); + $puml = $this->formatter->format($puml); + $outPath = $input->getArgument(self::ARG_OUTPUT_PATH); + if ($outPath === 'php://stdout') { + $output->write($puml, false,Output::OUTPUT_RAW); + } else { + if (file_put_contents($outPath, $puml) === false) { + throw new PhPumlException(sprintf('Output file "%s" is not writable.', $outPath)); + } + } return 0; } } \ No newline at end of file diff --git a/src/Formatter/Formatter.php b/src/Formatter/Formatter.php new file mode 100644 index 0000000..ada29e4 --- /dev/null +++ b/src/Formatter/Formatter.php @@ -0,0 +1,81 @@ +options = $options; + $this->formatters = $formatters; + } + + /** + * @param string|null $puml + * + * @return string + * @throws FormatterException + */ + public function format(?string $puml): string + { + if ($puml === null) { + return ''; + } + + try { + $format = $this->options->get('format'); + } catch (OptionsException $e) { + throw new FormatterException('Output format not specified.', 1609025248, $e); + } + + return $this->getFormatterByFormat($format)->format($puml); + } + + /** + * @param string $format + * + * @return FormatterInterface + * @throws FormatterException + */ + private function getFormatterByFormat(string $format): FormatterInterface + { + return $format === 'puml' ? + $this->getFormatterByClassname(NullFormatterStrategy::class) + : $this->getFormatterByClassname(PlantUmlFormatterStrategy::class); + } + + /** + * @param string $class + * + * @return FormatterInterface + * @throws FormatterException + */ + private function getFormatterByClassname(string $class): FormatterInterface + { + foreach ($this->formatters as $formatter) { + if (is_a($formatter, $class)) { + return $formatter; + } + } + throw new FormatterException(sprintf('Formatter "%s" not found.', $class)); + } +} diff --git a/src/Formatter/FormatterException.php b/src/Formatter/FormatterException.php new file mode 100644 index 0000000..8db89e6 --- /dev/null +++ b/src/Formatter/FormatterException.php @@ -0,0 +1,12 @@ +rootDir = $rootDir; + $this->options = $options; + } + + /** + * @param string $puml + * + * @return string + * @throws FormatterException + */ + public function format(string $puml): string + { + $proc = new Process(['java', '-jar', $this->getPlantUmlJarPath(), '-pipe', $this->getPlantUmlParameterForFormat($this->options->format)], $this->rootDir); + $proc->setInput($puml); + try { + $proc->mustRun(); + } catch (ProcessFailedException $e) { + throw new FormatterException('PlantUML run failed.', 1609025808, $e); + } catch (ProcessTimedOutException $e) { + throw new FormatterException('PlantUML run timed out.', 1609025809, $e); + } + return $proc->getOutput(); + } + + /** + * Get plantuml.jar cli parameter for an output format + * + * @param $format + * + * @return string + */ + private function getPlantUmlParameterForFormat(string $format): string + { + return sprintf('-t%s', $format); + } + + /** + * @throws FormatterException + */ + private function getPlantUmlJarPath(): string + { try { + $path = realpath($this->options->get('plantuml-path')); + } catch (OptionsException $e) { + throw new FormatterException('Error determining plantuml-path parameter.', 1609059884, $e); + } + if ($path === false) { + throw new FormatterException( + 'Format unavailable, plantuml.jar not found. Either provide a valid path ' + . 'via the plantuml-path parameter or install the optional composer package jawira/plantuml.' + ); + } + return $path; + } +} diff --git a/src/Options/Option.php b/src/Options/Option.php new file mode 100644 index 0000000..1daa7ba --- /dev/null +++ b/src/Options/Option.php @@ -0,0 +1,59 @@ +config = $config; + } + + public function isArray(): bool + { + return $this->config[Conf::KEY_IS_ARRAY] ?? false; + } + + /** @return array|string */ + public function getValue() + { + return $this->config[Conf::KEY_VALUE]; + } + + public function __toString(): string + { + return $this->getValue(); + } + + public function getValidValues(): ?array + { + return $this->config[Conf::KEY_VALUES]; + } + + public function getName(): string + { + return $this->config[Conf::KEY_NAME]; + } + + public function getShortName(): ?string + { + return $this->config[Conf::KEY_NAME_SHORT] ?? null; + } + + public function getDescription(): ?string + { + return $this->config[Conf::KEY_DESCRIPTION] ?? null; + } +} diff --git a/src/Options/OptionConfiguration.php b/src/Options/OptionConfiguration.php new file mode 100644 index 0000000..47843a7 --- /dev/null +++ b/src/Options/OptionConfiguration.php @@ -0,0 +1,18 @@ + [self::VALUE_EXCLUDE_DEFAULT], - self::OPTION_INCLUDE => [self::VALUE_INCLUDE_DEFAULT] - ]; - - /** @var array $flags option flags for command */ - private static $flags = [ - self::OPTION_EXCLUDE => InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, - self::OPTION_INCLUDE => InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY - ]; - /** @var array $options */ - protected $options; + private $options; /** + * Options constructor. + * * @param array $options - * @return Options + * + * @throws OptionsException */ - public static function fromArray(array $options): Options + public function __construct(array $options) { - return new self( - array_merge(self::$defaults, $options) - ); + $this->validateConfig($options); + $this->options = $options; } /** - * @return array + * @param array $options + * + * @throws OptionsException */ - public static function getDefaults(): array + private function validateConfig(array $options) { - return self::$defaults; + foreach ($options as $name => $option) { + foreach ([Conf::KEY_VALUE] as $requiredField) { + if (!array_key_exists($requiredField, $option)) { + throw new OptionsException(sprintf('Option "%s" is missing required field "%s".', $name, $requiredField)); + } + } + } } /** - * @return array + * {@inheritdoc} */ - public static function getFlags(string $option): ?int + public function getIterator() { - return self::$flags[$option] ?? null; + foreach ($this->options as $name => $option) { + $option[Conf::KEY_NAME] = $name; + yield $name => new Option($option); + } } /** - * Options constructor. - * @param array $options + * @param array $values + * + * @return Options + * @throws OptionsException */ - private function __construct(array $options) + public function setValues(array $values): self { - $this->options = $options; + foreach ($values as $name => $value) { + if ($this->has($name)) { + $this->set($name, $value); + } + } + return $this; + } + + /** + * @param string $name + * + * @return bool + */ + public function has(string $name): bool + { + return array_key_exists($name, $this->options); } /** @@ -77,10 +90,58 @@ private function __construct(array $options) */ public function __get(string $name) { - if (!array_key_exists($name, $this->options)) { - throw new OptionsException(sprintf('Unknown option "%s".', $name)); - } - return $this->options[$name]; + return $this->get($name); + } + + /** + * @param string $name + * + * @return mixed + * @throws OptionsException + */ + public function get(string $name) + { + $this->validate($name); + return $this->options[$name][Conf::KEY_VALUE] ?? null; + } + + /** + * @param string $name + * @return Option + * @throws OptionsException + */ + public function getOption(string $name): OptionInterface + { + $this->validate($name); + $option = $this->options[$name]; + $option[Conf::KEY_NAME] = $name; + return new Option($option); + } + + /** + * @param string $name + * @param $value + * + * @return Options + * @throws OptionsException + */ + public function __set(string $name, $value): self + { + return $this->set($name, $value); + } + + /** + * @param string $name + * @param $value + * + * @return self + * @throws OptionsException + */ + public function set(string $name, $value): self + { + $this->validate($name, $value); + $this->options[$name][Conf::KEY_VALUE] = $value; + return $this; } /** @@ -90,4 +151,23 @@ public function jsonSerialize() { return $this->options; } + + /** + * @param string $name + * @param null $value + * + * @throws OptionsException + */ + private function validate(string $name, $value = null) + { + if (!$this->has($name)) { + throw new OptionsException(sprintf('Unknown option "%s".', $name)); + } + if ($value !== null + && array_key_exists(Conf::KEY_VALUES, $this->options[$name]) + && !in_array($value, $this->options[$name][Conf::KEY_VALUES]) + ) { + throw new OptionsException(sprintf('Value "%s" is not valid for option "%s".', $value, $name)); + } + } } diff --git a/src/Service/PhPuml.php b/src/Service/PhPuml.php index bdb7df2..3eddf9e 100644 --- a/src/Service/PhPuml.php +++ b/src/Service/PhPuml.php @@ -6,7 +6,6 @@ use Jhofm\PhPuml\CodeProvider\CodeProvider; use Jhofm\PhPuml\PhPumlException; -use Jhofm\PhPuml\Options\Options; use Jhofm\PhPuml\Relation\RelationInferrer; use Jhofm\PhPuml\Renderer\ClassLikeRenderer; use Jhofm\PhPuml\Renderer\RelationRenderer; @@ -77,15 +76,14 @@ public function __construct( /** * @param string $input - * @param Options $options * * @return string * @throws PhPumlException */ - public function generatePuml(string $input, Options $options): string + public function generatePuml(string $input): string { $puml = self::PUML_HEADER; - $codeFiles = $this->codeProvider->getCode($input, $options); + $codeFiles = $this->codeProvider->getCode($input); foreach ($codeFiles as $path => $code) { try { $nodes = $this->parser->parse($code);