From 94179dc1b58b619d2d647686048d5aa474d7123a Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Mon, 11 Mar 2024 00:36:30 +0400 Subject: [PATCH] Refactor workflows to focus on demos Renamed workflow from "CI" to "Demo" to emphasize its focus on demonstrating features and use cases. Removed outdated or unused configuration options from the workflow file. Cleaned up commands and added a new command to run demo for GitHub. Updated README, composer.json, and other related files to reflect the changes being made to the workflow. --- .github/workflows/demo.yml | 50 +++++++ Makefile | 26 ++++ README.md | 10 +- composer.json | 7 +- composer.lock | 204 ++++++++++++++++++++--------- src/Commands/ValidateCsv.php | 48 ++++++- src/Csv/Column.php | 10 +- src/Csv/CsvFile.php | 6 +- src/Schema.php | 2 +- src/Validators/ErrorSuite.php | 84 ++++++++++-- tests/Blueprint/MiscTest.php | 2 +- tests/Blueprint/ValidatorTest.php | 2 +- tests/schemas/example_full.yml | 4 +- tests/schemas/simple_no_header.yml | 2 +- 14 files changed, 358 insertions(+), 99 deletions(-) create mode 100644 .github/workflows/demo.yml diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml new file mode 100644 index 00000000..03748da2 --- /dev/null +++ b/.github/workflows/demo.yml @@ -0,0 +1,50 @@ +# +# JBZoo Toolbox - Csv-Blueprint. +# +# This file is part of the JBZoo Toolbox project. +# For the full copyright and license information, please view the LICENSE +# file that was distributed with this source code. +# +# @license MIT +# @copyright Copyright (C) JBZoo.com, All rights reserved. +# @see https://github.com/JBZoo/Csv-Blueprint +# + +name: Demo + +on: + pull_request: + branches: + - "*" + push: + branches: + - 'master' + +env: + COLUMNS: 120 + TERM_PROGRAM: Hyper + +jobs: + pure-php: + name: Pure PHP + runs-on: ubuntu-latest + strategy: + matrix: + php-version: [ 8.1, 8.2, 8.3 ] + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + tools: composer + + - name: Build the Project + run: make update --no-print-directory + + - name: 🧪 PHPUnit Tests + run: make demo-github diff --git a/Makefile b/Makefile index 0cbdad5b..00893b46 100644 --- a/Makefile +++ b/Makefile @@ -25,3 +25,29 @@ update: ##@Project Install/Update all 3rd party dependencies test-all: ##@Project Run all project tests at once @make test @make codestyle + + +demo-valid: ##@Project Run demo valid CSV + $(call title,"Demo - Valid CSV") + @${PHP_BIN} ./csv-blueprint validate:csv \ + --csv=./tests/fixtures/demo.csv \ + --schema=./tests/schemas/demo_valid.yml + + +demo-invalid: ##@Project Run demo invalid CSV + $(call title,"Demo - Invalid CSV") + @${PHP_BIN} ./csv-blueprint validate:csv \ + --csv=./tests/fixtures/demo.csv \ + --schema=./tests/schemas/demo_invalid.yml \ + --output=github + +demo-gihub: ##@Project Run demo invalid CSV + @${PHP_BIN} ./csv-blueprint validate:csv \ + --csv=./tests/fixtures/demo.csv \ + --schema=./tests/schemas/demo_invalid.yml \ + --output=github + + +demo: ##@Project Run all demo commands + @make demo-valid + @make demo-invalid diff --git a/README.md b/README.md index b2923dad..2a7cbfe1 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ composer require jbzoo/csv-blueprint ```yml # It's a full example of the CSV schema file in YAML format. -csv_structure: # Here are default values. You can skip this section if you don't need to override the default values +csv: # Here are default values. You can skip this section if you don't need to override the default values header: true # If the first row is a header. If true, name of each column is required delimiter: , # Delimiter character in CSV file quote_char: \ # Quote character in CSV file @@ -33,7 +33,7 @@ csv_structure: # Here are default values. You can skip this section if you don't bom: false # If the file has a BOM (Byte Order Mark) at the beginning (Experimental) columns: - - name: "csv_header_name" # Any custom name of the column in the CSV file (first row). Required if "csv_structure.header" is true. + - name: "csv_header_name" # Any custom name of the column in the CSV file (first row). Required if "csv.header" is true. description: "Lorem ipsum" # Optional. Description of the column. Not used in the validation process. rules: # You can use the rules in any combination. Or not use any of them. @@ -88,7 +88,7 @@ columns: ```json { - "csv_structure" : { + "csv" : { "header" : true, "delimiter" : ",", "quote_char" : "\\", @@ -96,7 +96,7 @@ columns: "encoding" : "utf-8", "bom" : false }, - "columns" : [ + "columns" : [ { "name" : "csv_header_name", "description" : "Lorem ipsum", @@ -147,7 +147,7 @@ columns: declare(strict_types=1); return [ - 'csv_structure' => [ + 'csv' => [ 'header' => true, 'delimiter' => ',', 'quote_char' => '\\', diff --git a/composer.json b/composer.json index f944a76a..b78cd5e6 100644 --- a/composer.json +++ b/composer.json @@ -28,17 +28,18 @@ "require" : { "php" : "^8.1", "ext-mbstring" : "*", + "jbzoo/data" : "^7.1", "jbzoo/cli" : "^7.1", + "jbzoo/utils" : "^7.1", "league/csv" : "^9.15", "fakerphp/faker" : "^1.23", - "jbzoo/utils" : "^7.1" + "jbzoo/ci-report-converter": "^7.2" }, "require-dev" : { "roave/security-advisories" : "dev-latest", - "jbzoo/toolbox-dev" : "^7.1", - "jbzoo/markdown": "^7.0" + "jbzoo/toolbox-dev" : "^7.1" }, "autoload" : { diff --git a/composer.lock b/composer.lock index 1e70afbf..e87cb2a4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "34ca1de55f9be82342e8922b281d6923", + "content-hash": "661fc4e2593d0d9598e69e2de68aca8f", "packages": [ { "name": "bluepsyduck/symfony-process-manager", @@ -126,6 +126,92 @@ }, "time": "2024-01-02T13:46:09+00:00" }, + { + "name": "jbzoo/ci-report-converter", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/CI-Report-Converter.git", + "reference": "b411b01d673f2d70cf8edf8693352c6ff831bd8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/CI-Report-Converter/zipball/b411b01d673f2d70cf8edf8693352c6ff831bd8c", + "reference": "b411b01d673f2d70cf8edf8693352c6ff831bd8c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-hash": "*", + "ext-simplexml": "*", + "jbzoo/cli": "^7.1.8", + "jbzoo/data": "^7.1", + "jbzoo/markdown": "^7.0", + "jbzoo/utils": "^7.1", + "php": "^8.1", + "symfony/console": ">=6.4" + }, + "require-dev": { + "jbzoo/mermaid-php": "^7.2", + "jbzoo/toolbox-dev": "^7.1", + "roave/security-advisories": "dev-master" + }, + "bin": [ + "ci-report-converter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "JBZoo\\CIReportConverter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Denis Smetannikov", + "email": "admin@jbzoo.com", + "role": "lead" + } + ], + "description": "The tool converts different error reporting standards for deep integration with popular CI systems (TeamCity, IntelliJ IDEA, GitHub Actions, etc)", + "keywords": [ + "Codestyle", + "Github Actions", + "IntelliJ IDEA", + "PHPStan", + "actions", + "checkstyle", + "ci", + "continuous integration", + "github", + "inspections", + "junit", + "phan", + "phpcs", + "phploc", + "phpmd", + "phpmnd", + "phpstorm", + "pmd", + "psalm", + "teamcity", + "teamcity-inspections", + "tests" + ], + "support": { + "issues": "https://github.com/JBZoo/CI-Report-Converter/issues", + "source": "https://github.com/JBZoo/CI-Report-Converter/tree/7.2.1" + }, + "time": "2024-01-28T14:03:00+00:00" + }, { "name": "jbzoo/cli", "version": "7.1.8", @@ -344,6 +430,63 @@ }, "time": "2024-01-28T08:57:37+00:00" }, + { + "name": "jbzoo/markdown", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/Markdown.git", + "reference": "02e9d756ed91d33c63a7794db1279af56e4da5e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/Markdown/zipball/02e9d756ed91d33c63a7794db1279af56e4da5e9", + "reference": "02e9d756ed91d33c63a7794db1279af56e4da5e9", + "shasum": "" + }, + "require": { + "jbzoo/utils": "^7.1", + "php": "^8.1" + }, + "require-dev": { + "jbzoo/toolbox-dev": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "JBZoo\\Markdown\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Denis Smetannikov", + "email": "admin@jbzoo.com", + "role": "lead" + } + ], + "description": "Tools to render markdown text from PHP code", + "keywords": [ + "Rendering", + "jbzoo", + "markdown", + "readme", + "text" + ], + "support": { + "issues": "https://github.com/JBZoo/Markdown/issues", + "source": "https://github.com/JBZoo/Markdown/tree/7.0.1" + }, + "time": "2024-01-28T12:43:57+00:00" + }, { "name": "jbzoo/utils", "version": "7.1.2", @@ -2803,63 +2946,6 @@ }, "time": "2021-03-31T09:21:47+00:00" }, - { - "name": "jbzoo/markdown", - "version": "7.0.1", - "source": { - "type": "git", - "url": "https://github.com/JBZoo/Markdown.git", - "reference": "02e9d756ed91d33c63a7794db1279af56e4da5e9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/JBZoo/Markdown/zipball/02e9d756ed91d33c63a7794db1279af56e4da5e9", - "reference": "02e9d756ed91d33c63a7794db1279af56e4da5e9", - "shasum": "" - }, - "require": { - "jbzoo/utils": "^7.1", - "php": "^8.1" - }, - "require-dev": { - "jbzoo/toolbox-dev": "^7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "7.x-dev" - } - }, - "autoload": { - "psr-4": { - "JBZoo\\Markdown\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Denis Smetannikov", - "email": "admin@jbzoo.com", - "role": "lead" - } - ], - "description": "Tools to render markdown text from PHP code", - "keywords": [ - "Rendering", - "jbzoo", - "markdown", - "readme", - "text" - ], - "support": { - "issues": "https://github.com/JBZoo/Markdown/issues", - "source": "https://github.com/JBZoo/Markdown/tree/7.0.1" - }, - "time": "2024-01-28T12:43:57+00:00" - }, { "name": "jbzoo/phpunit", "version": "7.1.0", @@ -5362,7 +5448,7 @@ "react/http": ">=0.7,<1.9", "really-simple-plugins/complianz-gdpr": "<6.4.2", "redaxo/source": "<=5.15.1", - "remdex/livehelperchat": "<3.99", + "remdex/livehelperchat": "<4.29", "reportico-web/reportico": "<=7.1.21", "rhukster/dom-sanitizer": "<1.0.7", "rmccue/requests": ">=1.6,<1.8", diff --git a/src/Commands/ValidateCsv.php b/src/Commands/ValidateCsv.php index 7db44667..5e13da01 100644 --- a/src/Commands/ValidateCsv.php +++ b/src/Commands/ValidateCsv.php @@ -41,6 +41,14 @@ protected function configure(): void 's', InputOption::VALUE_REQUIRED, 'Schema rule filepath', + ) + ->addOption( + 'output', + 'o', + InputOption::VALUE_REQUIRED, + 'Report output format. Available options: ' . + \implode(', ', ErrorSuite::getAvaiableRenderFormats()) . '', + ErrorSuite::RENDER_TABLE, ); parent::configure(); @@ -54,12 +62,25 @@ protected function executeAction(): int $csvFile = new CsvFile($csvFilename, $schemaFilename); $errorSuite = $csvFile->validate(); if ($errorSuite->count() > 0) { - $this->_($errorSuite->render(ErrorSuite::RENDER_TEXT), OutLvl::ERROR); + $this->_( + $errorSuite->render($this->getOptString('output')), + $this->isTextMode() ? OutLvl::E : OutLvl::DEFAULT, + ); - throw new Exception('CSV file is not valid! Found ' . $errorSuite->count() . ' errors.'); + if ($this->isTextMode()) { + $this->_( + 'CSV file is not valid! ' . + 'Found ' . $errorSuite->count() . ' errors.', + OutLvl::ERROR, + ); + } + + return self::FAILURE; } - $this->_('Looks good!'); + if ($this->isTextMode()) { + $this->_('Looks good!'); + } return self::SUCCESS; } @@ -72,7 +93,9 @@ private function getCsvFilepath(): string throw new Exception("CSV file not found: {$csvFilename}"); } - $this->_('CSV : ' . \str_replace(PATH_ROOT, '.', \realpath($csvFilename))); + if ($this->isTextMode()) { + $this->_('CSV : ' . \str_replace(PATH_ROOT, '.', \realpath($csvFilename))); + } return $csvFilename; } @@ -85,8 +108,23 @@ private function getSchemaFilepath(): string throw new Exception("Schema file not found: {$schemaFilename}"); } - $this->_('Schema : ' . \str_replace(PATH_ROOT, '.', \realpath($schemaFilename))); + if ($this->isTextMode()) { + $this->_('Schema : ' . \str_replace(PATH_ROOT, '.', \realpath($schemaFilename))); + } return $schemaFilename; } + + private function isTextMode(): bool + { + return $this->getOutputMode() === ErrorSuite::RENDER_TEXT + || $this->getOutputMode() === ErrorSuite::RENDER_GITHUB + || $this->getOutputMode() === ErrorSuite::RENDER_TEAMCITY + || $this->getOutputMode() === ErrorSuite::RENDER_TABLE; + } + + private function getOutputMode(): string + { + return $this->getOptString('output', ErrorSuite::RENDER_TABLE, ErrorSuite::getAvaiableRenderFormats()); + } } diff --git a/src/Csv/Column.php b/src/Csv/Column.php index 23c248a2..28bc4546 100644 --- a/src/Csv/Column.php +++ b/src/Csv/Column.php @@ -35,10 +35,10 @@ final class Column 'aggregate_rules' => [], ]; - private int $id; - private Data $column; - private array $rules; - private array $aggregateRules; + private int $id; + private Data $column; + private array $rules; + private array $aggregateRules; public function __construct(int $id, array $config) { @@ -65,7 +65,7 @@ public function getDescription(): string public function getHumanName(): string { - return \trim($this->getName() . ' (' . $this->getId() . ')'); + return $this->getId() . ':' . \trim($this->getName()); } public function getKey(): string diff --git a/src/Csv/CsvFile.php b/src/Csv/CsvFile.php index 97165b59..744adac4 100644 --- a/src/Csv/CsvFile.php +++ b/src/Csv/CsvFile.php @@ -44,7 +44,7 @@ public function __construct(string $csvFilename, null|array|string $csvSchemaFil public function getCsvFilename(): string { - return $this->csvFilename; + return \str_replace(PROJECT_ROOT, '.', $this->csvFilename); } public function getCsvStructure(): ParseConfig @@ -76,7 +76,7 @@ public function getRecordsChunk(int $offset = 0, int $limit = -1): \League\Csv\R public function validate(bool $quickStop = false): ErrorSuite { - $errors = new ErrorSuite(); + $errors = new ErrorSuite($this->getCsvFilename()); $errors->addErrorSuit($this->validateHeader()) ->addErrorSuit($this->validateEachCell($quickStop)) @@ -122,7 +122,7 @@ private function validateHeader(): ErrorSuite foreach ($this->schema->getColumns() as $column) { if ($column->getName() === '') { $error = new Error( - 'csv_structure.header', + 'csv.header', "Property \"name\" is not defined in schema: \"{$this->schema->getFilename()}\"", $column->getHumanName(), 1, diff --git a/src/Schema.php b/src/Schema.php index c12e7285..2d2f5b35 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -62,7 +62,7 @@ public function getFilename(): string public function getCsvStructure(): ParseConfig { - return new ParseConfig($this->data->getArray('csv_structure')); + return new ParseConfig($this->data->getArray('csv')); } /** diff --git a/src/Validators/ErrorSuite.php b/src/Validators/ErrorSuite.php index 115f2714..f97c6119 100644 --- a/src/Validators/ErrorSuite.php +++ b/src/Validators/ErrorSuite.php @@ -16,14 +16,30 @@ namespace JBZoo\CsvBlueprint\Validators; +use JBZoo\CIReportConverter\Converters\GithubCliConverter; +use JBZoo\CIReportConverter\Converters\GitLabJsonConverter; +use JBZoo\CIReportConverter\Converters\JUnitConverter; +use JBZoo\CIReportConverter\Converters\TeamCityTestsConverter; +use JBZoo\CIReportConverter\Formats\Source\SourceSuite; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Output\BufferedOutput; + final class ErrorSuite { - public const RENDER_TEXT = 'plain'; - public const RENDER_LIST = 'list'; + public const RENDER_TEXT = 'text'; + public const RENDER_TABLE = 'table'; + public const RENDER_TEAMCITY = 'teamcity'; + public const RENDER_GITLAB = 'gitlab'; + public const RENDER_GITHUB = 'github'; + public const RENDER_JUNIT = 'junit'; /** @var Error[] */ private array $errors = []; + public function __construct(private ?string $csvFilename = null) + { + } + public function __toString(): string { return $this->render(self::RENDER_TEXT); @@ -35,15 +51,20 @@ public function render(string $mode = self::RENDER_TEXT): string return ''; } - if ($mode === self::RENDER_TEXT) { - return $this->renderPlainText(); - } - - if ($mode === self::RENDER_LIST) { - return $this->renderList(); + $map = [ + self::RENDER_TEXT => fn () => $this->renderPlainText(), + self::RENDER_TABLE => fn () => $this->renderTable(), + self::RENDER_GITHUB => fn () => (new GithubCliConverter())->fromInternal($this->prepareSourceSuite()), + self::RENDER_GITLAB => fn () => (new GitLabJsonConverter())->fromInternal($this->prepareSourceSuite()), + self::RENDER_TEAMCITY => fn () => (new TeamCityTestsConverter())->fromInternal($this->prepareSourceSuite()), + self::RENDER_JUNIT => fn () => (new JUnitConverter())->fromInternal($this->prepareSourceSuite()), + ]; + + if (isset($map[$mode])) { + return $map[$mode](); } - throw new Exception('Unknown error render mode: ' . $mode); + throw new Exception("Unknown error render mode: {$mode}"); } /** @@ -86,6 +107,18 @@ public function get(int $index): ?Error return $this->errors[$index] ?? null; } + public static function getAvaiableRenderFormats(): array + { + return [ + self::RENDER_TEXT, + self::RENDER_TABLE, + self::RENDER_GITHUB, + self::RENDER_GITLAB, + self::RENDER_TEAMCITY, + self::RENDER_JUNIT, + ]; + } + private function renderPlainText(): string { $result = []; @@ -97,14 +130,39 @@ private function renderPlainText(): string return \implode("\n", $result); } - private function renderList(): string + private function renderTable(): string { - $result = []; + $buffer = new BufferedOutput(); + $table = (new Table($buffer)) + ->setHeaderTitle($this->csvFilename) + ->setFooterTitle($this->csvFilename) + ->setHeaders(['Line', 'id:Column', 'Rule', 'Message']) + ->setColumnMaxWidth(0, 10) + ->setColumnMaxWidth(1, 20) + ->setColumnMaxWidth(2, 20) + ->setColumnMaxWidth(3, 60); foreach ($this->errors as $error) { - $result[] = (string)$error; + $table->addRow([$error->getLine(), $error->getColumnName(), $error->getRuleCode(), $error->getMessage()]); + } + + $table->render(); + + return $buffer->fetch(); + } + + private function prepareSourceSuite(): SourceSuite + { + $suite = new SourceSuite($this->csvFilename); + + foreach ($this->errors as $error) { + $caseName = $error->getRuleCode() . ' at column ' . $error->getColumnName(); + $case = $suite->addTestCase($caseName); + $case->line = $error->getLine(); + $case->file = $this->csvFilename; + $case->errOut = $error->getMessage(); } - return ' * ' . \implode("\n * ", $result); + return $suite; } } diff --git a/tests/Blueprint/MiscTest.php b/tests/Blueprint/MiscTest.php index 91b2d20a..08702b3d 100644 --- a/tests/Blueprint/MiscTest.php +++ b/tests/Blueprint/MiscTest.php @@ -86,7 +86,7 @@ public function testFullListOfRules(): void public function testCsvStrutureDefaultValues(): void { - $defaultsInDoc = yml(PROJECT_ROOT . '/schema-examples/full.yml')->findArray('csv_structure'); + $defaultsInDoc = yml(PROJECT_ROOT . '/schema-examples/full.yml')->findArray('csv'); $schema = new Schema([]); $schema->getCsvStructure()->getArrayCopy(); diff --git a/tests/Blueprint/ValidatorTest.php b/tests/Blueprint/ValidatorTest.php index 70101ef9..ef039f6a 100644 --- a/tests/Blueprint/ValidatorTest.php +++ b/tests/Blueprint/ValidatorTest.php @@ -72,7 +72,7 @@ public function testNoName(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule(null, 'not_empty', true)); isSame( - '"csv_structure.header" at line 1, column "(0)". ' . + '"csv.header" at line 1, column "(0)". ' . 'Property "name" is not defined in schema: "_custom_array_".', (string)$csv->validate(), ); diff --git a/tests/schemas/example_full.yml b/tests/schemas/example_full.yml index 9313d861..51fdb55f 100644 --- a/tests/schemas/example_full.yml +++ b/tests/schemas/example_full.yml @@ -27,7 +27,7 @@ includes: # Alias is always required - ../path/schema_3.yml as alias_3 # Relative path based on the current schema path. Go up one level. -csv_structure: # How to parse file before validation +csv: # How to parse file before validation inherit: alias_1 # Inherited from another schema. Options above will overwrite inherited options. bom: false # true - file starts with BOM, false - no BOM delimiter: , # delimiter char to separate cells @@ -43,7 +43,7 @@ csv_structure: # How to parse file before validation columns: - invalid_option: true # Just to test super minimal configuration - - name: General available options # Can be optional if csv_structure\header: false. If set, then header must contain this value + - name: General available options # Can be optional if csv\header: false. If set, then header must contain this value description: Some description # Any custom description type: some_type # Can be optional. At your own risk! If empty, then use Validator\Base required: true # If true, then column must be present in the file diff --git a/tests/schemas/simple_no_header.yml b/tests/schemas/simple_no_header.yml index 1bf25be3..ebcb82ac 100644 --- a/tests/schemas/simple_no_header.yml +++ b/tests/schemas/simple_no_header.yml @@ -10,7 +10,7 @@ # @see https://github.com/JBZoo/Csv-Blueprint # -csv_structure: +csv: header: false columns: