Page MenuHomeDevCentral

No OneTemporary

diff --git a/report/.arcconfig b/report/.arcconfig
new file mode 100644
index 0000000..cc3485c
--- /dev/null
+++ b/report/.arcconfig
@@ -0,0 +1,5 @@
+{
+ "repository.callsign": "KREPORT",
+ "phabricator.uri": "https://devcentral.nasqueron.org",
+ "unit.engine": "PhpunitTestEngine"
+}
diff --git a/report/.arclint b/report/.arclint
new file mode 100644
index 0000000..8ecd880
--- /dev/null
+++ b/report/.arclint
@@ -0,0 +1,32 @@
+{
+ "exclude": [
+ "(^vendor/)"
+ ],
+ "linters": {
+ "chmod": {
+ "type": "chmod"
+ },
+ "filename": {
+ "type": "filename"
+ },
+ "merge-conflict": {
+ "type": "merge-conflict"
+ },
+ "php": {
+ "type": "php",
+ "include": "(\\.php$)"
+ },
+ "phpcs": {
+ "type": "phpcs",
+ "bin": "vendor/bin/phpcs",
+ "phpcs.standard": "phpcs.xml",
+ "include": [
+ "(^src/.*\\.php$)",
+ "(^tests/.*\\.php$)"
+ ]
+ },
+ "spelling": {
+ "type": "spelling"
+ }
+ }
+}
diff --git a/report/.gitignore b/report/.gitignore
new file mode 100644
index 0000000..9aa7c69
--- /dev/null
+++ b/report/.gitignore
@@ -0,0 +1,3 @@
+# Composer
+/vendor/
+composer.lock
diff --git a/report/.phan/config.php b/report/.phan/config.php
new file mode 100644
index 0000000..956b5f3
--- /dev/null
+++ b/report/.phan/config.php
@@ -0,0 +1,12 @@
+<?php
+
+return [
+ 'target_php_version' => '8.0',
+ 'directory_list' => [
+ 'src',
+ ],
+ 'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@',
+ 'exclude_analysis_directory_list' => [
+ 'vendor/'
+ ],
+];
diff --git a/report/README.md b/report/README.md
new file mode 100644
index 0000000..43ab688
--- /dev/null
+++ b/report/README.md
@@ -0,0 +1,54 @@
+# keruald/report
+
+Allow to build a report and output it.
+
+## Report skeleton
+
+### Introduction
+
+A report is a collection of sections, a general title, and some metadata.
+
+A section is a collection of entries, and a title. They can also be thought as the chapters of a book.
+
+An entry is text and a title.
+
+That gives the following hierarchy:
+
+```
+Report title (sections) properties
+ ReportSection title (entries)
+ ReportEntry title text
+```
+
+A full example can be found in the `tests/WithSampleReport.php` file.
+
+### Simplified report
+
+You can build a simplified version using only the ReportSection class:
+
+```php
+use Keruald\Reporting\ReportSection;
+
+$report = new ReportSection("A simple report about historical geometric problems");
+$report->push("Issue 1", "Can we square a circle?");
+$report->push("Issue 2", "Can we divise an angle by 3?");
+$report->push("Issue 2", "Can we double a cube?");
+
+print_r($report);
+```
+
+## Output
+
+The library provides HTML and Markdown output.
+
+Examples of such output can be found in the `tests/data` folder.
+
+Those output classes aren't mandatory to use to present the results:
+the report data structure can be easily walked with foreach loops
+to manipulate it.
+
+## Uses
+
+The **keruald/healthcheck** library uses this reporting library to generate
+a site health check, and present the results to help to remediate to
+the issues detected.
diff --git a/report/composer.json b/report/composer.json
new file mode 100644
index 0000000..4f86d37
--- /dev/null
+++ b/report/composer.json
@@ -0,0 +1,18 @@
+{
+ "name": "keruald/report",
+ "description": "Build a report with sections and entries. Markdown, PHP objects and HTML outputs.",
+ "type": "library",
+ "require-dev": {
+ "nasqueron/codestyle": "^0.0.1",
+ "phan/phan": "^5.3",
+ "phpunit/phpunit": "^9.5",
+ "squizlabs/php_codesniffer": "^3.6"
+ },
+ "license": "BSD-2-Clause",
+ "autoload": {
+ "psr-4": {
+ "Keruald\\Reporting\\": "src/",
+ "Keruald\\Reporting\\Tests\\": "tests/"
+ }
+ }
+}
diff --git a/report/phpcs.xml b/report/phpcs.xml
new file mode 100644
index 0000000..e33ff52
--- /dev/null
+++ b/report/phpcs.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<ruleset name="Nasqueron">
+ <rule ref="vendor/nasqueron/codestyle/CodeSniffer/ruleset.xml" />
+
+ <file>src</file>
+ <file>tests</file>
+</ruleset>
diff --git a/report/phpunit.xml b/report/phpunit.xml
new file mode 100644
index 0000000..f5c1939
--- /dev/null
+++ b/report/phpunit.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
+ bootstrap="vendor/autoload.php"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ stopOnFailure="false">
+ <php>
+ <ini name="display_errors" value="On" />
+ <ini name="display_startup_errors" value="On" />
+ <ini name="error_reporting" value="On" />
+ </php>
+ <testsuites>
+ <testsuite name="Unit tests">
+ <directory suffix="Test.php">./tests</directory>
+ </testsuite>
+ </testsuites>
+ <coverage processUncoveredFiles="true">
+ <include>
+ <directory suffix=".php">src/</directory>
+ </include>
+ </coverage>
+</phpunit>
diff --git a/report/src/Output/HTMLOutput.php b/report/src/Output/HTMLOutput.php
new file mode 100644
index 0000000..a722205
--- /dev/null
+++ b/report/src/Output/HTMLOutput.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Keruald\Reporting\Output;
+
+class HTMLOutput extends Output {
+
+ private function makeId ($name) : string {
+ return urlencode(strtolower(str_replace(' ', '-', $name)));
+ }
+
+ private static function encode (string $text) : string {
+ return htmlspecialchars($text);
+ }
+
+ public function render () : string {
+
+ $send = [];
+ $title = $this->report->title;
+ $send[] =
+ '<h1 id="' . $this->makeId($title) . '">' . self::encode($title)
+ . '</h1>';
+
+ foreach ($this->report->sections as $section) {
+ $title = $section->title;
+ $send[] =
+ '<h2 id="' . $this->makeId($title) . '">' .
+ self::encode($title) . '</h2>';
+ foreach ($section->entries as $entry) {
+ $title = $entry->title;
+ $send[] = '<h3 id="' . $this->makeId($title) . '">' .
+ self::encode($title) . '</h3>';
+
+ $text = explode("\n\n", $entry->text);
+
+ foreach ($text as $value) {
+ $send[] = '<p>' . self::encode($value) . '</p>';
+ }
+ }
+ }
+ $send[] = '<hr>';
+
+ $send[] = '<h2 id="report-properties">Report properties</h2>';
+ $send[] = '<table>';
+ $send[] = str_repeat(" ", 4) . '<tbody>';
+
+ $properties = $this->report->properties;
+
+ foreach ($properties as $key => $value) {
+ $send[] = str_repeat(" ", 4) . '<tr>';
+ $send[] = str_repeat(" ", 8) .
+
+ '<th>' . self::encode($key) . '</th>';
+ $send[] = str_repeat(" ", 8) .
+ '<td>' . self::encode($value) . '</td>';
+
+ $send[] = str_repeat(" ", 4) . '</tr>';
+ }
+ $send[] = str_repeat(" ", 4) . '</tbody>';
+ $send[] = '</table>';
+ $send[] = '';
+
+ return implode("\n", $send);
+ }
+}
diff --git a/report/src/Output/MarkdownOutput.php b/report/src/Output/MarkdownOutput.php
new file mode 100644
index 0000000..b9dfcb3
--- /dev/null
+++ b/report/src/Output/MarkdownOutput.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Keruald\Reporting\Output;
+
+class MarkdownOutput extends Output {
+
+ public function render () : string {
+
+
+ $send = [];
+ $send[] = '# ' . $this->report->title;
+ $send[] = '';
+ foreach ($this->report->sections as $section) {
+ $send[] = '## ' . $section->title;
+ $send[] = '';
+ foreach ($section->entries as $entry) {
+
+ $send[] = '### ' . $entry->title;
+ $send[] = '';
+ $send[] = $entry->text;
+ $send[] = '';
+ }
+ }
+
+ $send[] = '---';
+ $send[] = '';
+ $properties = $this->report->properties;
+ $propertyMaxLength = 0;
+ $maxValue = 0;
+ foreach ($properties as $key => $value) {
+ $propertyMaxLength = max($propertyMaxLength, strlen($key));
+ $maxValue = max($maxValue, strlen($value));
+ }
+ $propertyMaxLength = max(8, $propertyMaxLength);
+
+ $send[] = '| Property' . str_repeat(' ', $propertyMaxLength - 8) . ' | '
+ . str_repeat(' ', $maxValue) . ' |';
+
+ $send[] = '|' . str_repeat('-', $propertyMaxLength + 2) . '|'
+ . str_repeat('-', $maxValue + 2) . '|';
+
+ foreach ($properties as $key => $value) {
+ $send[] =
+ '| ' . $key . str_repeat(' ', $propertyMaxLength - strlen($key))
+ . ' | '
+ . $value . str_repeat(' ', $maxValue - strlen($value)) . ' |';
+ }
+
+ $send[] = '';
+
+ return implode("\n", $send);
+ }
+}
diff --git a/report/src/Output/Output.php b/report/src/Output/Output.php
new file mode 100644
index 0000000..7c33229
--- /dev/null
+++ b/report/src/Output/Output.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Keruald\Reporting\Output;
+
+use Keruald\Reporting\Report;
+
+abstract class Output {
+
+ ///
+ /// Properties
+ ///
+
+ protected Report $report;
+
+ ///
+ /// Abstract methods to implement
+ ///
+
+ public abstract function render () : string;
+
+ ///
+ /// Constructors
+ ///
+
+ public static function for (Report $report) : Output {
+ $output = new static();
+ $output->report = $report;
+
+ return $output;
+ }
+}
diff --git a/report/src/Output/XMLOutput.php b/report/src/Output/XMLOutput.php
new file mode 100644
index 0000000..48b547d
--- /dev/null
+++ b/report/src/Output/XMLOutput.php
@@ -0,0 +1,71 @@
+<?php
+
+
+namespace Keruald\Reporting\Output;
+
+
+class XMLOutput extends Output {
+
+ public function render () : string {
+
+ $document = xmlwriter_open_memory();
+
+ xmlwriter_set_indent($document, true);
+ xmlwriter_set_indent_string($document, ' ');
+
+ xmlwriter_start_document($document, '1.0', 'UTF-8');
+
+ xmlwriter_start_element($document, 'report');
+ xmlwriter_start_attribute($document, 'title');
+ xmlwriter_text($document, $this->report->title);
+ xmlwriter_end_attribute($document);
+
+ foreach ($this->report->sections as $section) {
+ xmlwriter_start_element($document, 'section');
+ xmlwriter_start_attribute($document, 'title');
+ xmlwriter_text($document, $section->title);
+ xmlwriter_end_attribute($document);
+
+ foreach ($section->entries as $entry) {
+ xmlwriter_start_element($document, 'entry');
+
+ xmlwriter_start_attribute($document, 'title');
+ xmlwriter_text($document, $entry->title);
+ xmlwriter_end_attribute($document);
+
+ xmlwriter_start_element($document, 'text');
+ xmlwriter_text($document, $entry->text);
+ xmlwriter_end_element($document);
+
+ xmlwriter_end_element($document);
+ }
+ xmlwriter_end_element($document); // section
+ }
+
+ xmlwriter_start_element($document, 'data');
+ xmlwriter_start_attribute($document, 'title');
+ xmlwriter_text($document, "Properties");
+ xmlwriter_end_attribute($document);
+
+ foreach ($this->report->properties as $key => $value) {
+ xmlwriter_start_element($document, 'entry');
+
+ xmlwriter_start_element($document, 'key');
+ xmlwriter_text($document, $key);
+ xmlwriter_end_element($document);
+
+ xmlwriter_start_element($document, 'value');
+ xmlwriter_text($document, $value);
+ xmlwriter_end_element($document);
+
+ xmlwriter_end_element($document);
+ }
+ xmlwriter_end_element($document); // data
+
+ xmlwriter_end_element($document); // report
+ xmlwriter_end_document($document);
+
+ return xmlwriter_output_memory($document);
+ }
+
+}
diff --git a/report/src/Report.php b/report/src/Report.php
new file mode 100644
index 0000000..2059295
--- /dev/null
+++ b/report/src/Report.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Keruald\Reporting;
+
+class Report {
+
+ public function __construct (
+ public string $title,
+
+ /**
+ * @var ReportSection[]
+ */
+ public array $sections = [],
+
+ /**
+ * @var array<string, mixed>
+ */
+ public array $properties = [],
+ ) { }
+
+ public function push (ReportSection $section) : void {
+ $this->sections[] = $section;
+ }
+
+ public function pushIfNotEmpty (ReportSection $report) : void {
+ if (!$report->isEmpty()) {
+ $this->push($report);
+ }
+ }
+
+}
diff --git a/report/src/ReportEntry.php b/report/src/ReportEntry.php
new file mode 100644
index 0000000..aa1b19f
--- /dev/null
+++ b/report/src/ReportEntry.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Keruald\Reporting;
+
+class ReportEntry {
+
+ public function __construct (
+ public string $title,
+ public string $text,
+ ) { }
+
+}
diff --git a/report/src/ReportSection.php b/report/src/ReportSection.php
new file mode 100644
index 0000000..ed36fe0
--- /dev/null
+++ b/report/src/ReportSection.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Keruald\Reporting;
+
+class ReportSection {
+
+ public function __construct (
+ public string $title,
+
+ /**
+ * @var ReportEntry[]
+ */
+ public array $entries = [],
+ ) { }
+
+ public function push (string $title, string $text) : void {
+ $this->entries[] = new ReportEntry($title, $text);
+ }
+
+ public function isEmpty () : bool {
+ return count($this->entries) === 0;
+ }
+
+}
diff --git a/report/tests/Output/HTMLOutputTest.php b/report/tests/Output/HTMLOutputTest.php
new file mode 100644
index 0000000..aa692eb
--- /dev/null
+++ b/report/tests/Output/HTMLOutputTest.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Keruald\Reporting\Tests\Output;
+
+use Keruald\Reporting\Output\HTMLOutput;
+use Keruald\Reporting\Report;
+
+use Keruald\Reporting\Tests\WithSampleReport;
+use PHPUnit\Framework\TestCase;
+
+class HTMLOutputTest extends TestCase {
+
+ use WithSampleReport;
+
+ ///
+ /// Initialization
+ //
+
+ public Report $report;
+
+ protected function setUp () : void {
+ $this->report = $this->buildSampleReport();
+ }
+
+ ///
+ /// Tests
+ //
+
+ public function testRender () : void {
+ $actual = HTMLOutput::for($this->report)
+ ->render();
+
+ $expected = file_get_contents($this->getDataDir() . "/report.html");
+
+ $this->assertEquals($expected, $actual);
+ }
+}
diff --git a/report/tests/Output/MarkdownOutputTest.php b/report/tests/Output/MarkdownOutputTest.php
new file mode 100644
index 0000000..749934c
--- /dev/null
+++ b/report/tests/Output/MarkdownOutputTest.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Keruald\Reporting\Tests\Output;
+
+use Keruald\Reporting\Output\MarkdownOutput;
+use Keruald\Reporting\Report;
+
+use Keruald\Reporting\Tests\WithSampleReport;
+use PHPUnit\Framework\TestCase;
+
+class MarkdownOutputTest extends TestCase {
+
+ use WithSampleReport;
+
+ ///
+ /// Initialization
+ //
+
+ public Report $report;
+
+ protected function setUp () : void {
+ $this->report = $this->buildSampleReport();
+ }
+
+ ///
+ /// Tests
+ //
+
+ public function testRender () : void {
+ $actual = MarkdownOutput::for($this->report)
+ ->render();
+
+ $expected = file_get_contents($this->getDataDir() . "/report.md");
+
+ $this->assertEquals($expected, $actual);
+ }
+}
diff --git a/report/tests/Output/XMLOutputTest.php b/report/tests/Output/XMLOutputTest.php
new file mode 100644
index 0000000..99d7c61
--- /dev/null
+++ b/report/tests/Output/XMLOutputTest.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Keruald\Reporting\Tests\Output;
+
+
+use Keruald\Reporting\Output\XMLOutput;
+use Keruald\Reporting\Report;
+
+use Keruald\Reporting\Tests\WithSampleReport;
+use PHPUnit\Framework\TestCase;
+
+class XMLOutputTest extends TestCase {
+
+ use WithSampleReport;
+
+ ///
+ /// Initialization
+ //
+
+ public Report $report;
+
+ protected function setUp () : void {
+ $this->report = $this->buildSampleReport();
+ }
+
+ ///
+ /// Tests
+ //
+
+ public function testRender () : void {
+ $actual = XMLOutput::for($this->report)
+ ->render();
+
+ $expected = file_get_contents($this->getDataDir() . "/report.xml");
+
+ $this->assertEquals($expected, $actual);
+ }
+
+}
diff --git a/report/tests/WithSampleReport.php b/report/tests/WithSampleReport.php
new file mode 100644
index 0000000..e6f140e
--- /dev/null
+++ b/report/tests/WithSampleReport.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Keruald\Reporting\Tests;
+
+use Keruald\Reporting\Report;
+use Keruald\Reporting\ReportEntry;
+use Keruald\Reporting\ReportSection;
+
+trait WithSampleReport {
+
+ public function getDataDir () : string {
+ return __DIR__ . "/data";
+ }
+
+ public function buildSampleReport () : Report {
+ $report = new Report("Sneakers");
+
+ // Section 1
+ $section = new ReportSection("Air Max");
+ $section->entries = [
+ new ReportEntry("Air Max 90", "One of the more icon color is the infrared."),
+ new ReportEntry("Air Max 95", "Launched in 1995, designed by Sergio Lozano."),
+ new ReportEntry("Air Max 97", <<<EOF
+Well highlighted Air bubble.
+
+Inspired by mountain bikes, while an urban legend quotes Japan bullet trains as inspiration.
+EOF
+),
+ ];
+ $report->push($section);
+
+ // Section 2
+ $section = new ReportSection("Other Nike Air");
+ $section->entries = [
+ new ReportEntry("Introduction", "Because there are other sneakers than Air Max."),
+ new ReportEntry("Air Force 1", "« Air Force 1. Zéro fan, que des fanatiques. » -- LTA"),
+ ];
+ $report->push($section);
+
+ // Section 3 — should be ignored as blank
+ $section = new ReportSection("Not cool sneakers");
+ // As all sneakers are cool, this is empty.
+ $report->pushIfNotEmpty($section);
+
+ // Section 4 — should be included even if blank
+ $section = new ReportSection("👟");
+ $report->push($section);
+
+ // Metadata
+ $report->properties = [
+ "Date" => "9999-99-99",
+ "Topic" => "Urban culture",
+ ];
+
+ return $report;
+ }
+
+}
diff --git a/report/tests/data/report.html b/report/tests/data/report.html
new file mode 100644
index 0000000..4464df2
--- /dev/null
+++ b/report/tests/data/report.html
@@ -0,0 +1,29 @@
+<h1 id="sneakers">Sneakers</h1>
+<h2 id="air-max">Air Max</h2>
+<h3 id="air-max-90">Air Max 90</h3>
+<p>One of the more icon color is the infrared.</p>
+<h3 id="air-max-95">Air Max 95</h3>
+<p>Launched in 1995, designed by Sergio Lozano.</p>
+<h3 id="air-max-97">Air Max 97</h3>
+<p>Well highlighted Air bubble.</p>
+<p>Inspired by mountain bikes, while an urban legend quotes Japan bullet trains as inspiration.</p>
+<h2 id="other-nike-air">Other Nike Air</h2>
+<h3 id="introduction">Introduction</h3>
+<p>Because there are other sneakers than Air Max.</p>
+<h3 id="air-force-1">Air Force 1</h3>
+<p>« Air Force 1. Zéro fan, que des fanatiques. » -- LTA</p>
+<h2 id="%F0%9F%91%9F">👟</h2>
+<hr>
+<h2 id="report-properties">Report properties</h2>
+<table>
+ <tbody>
+ <tr>
+ <th>Date</th>
+ <td>9999-99-99</td>
+ </tr>
+ <tr>
+ <th>Topic</th>
+ <td>Urban culture</td>
+ </tr>
+ </tbody>
+</table>
diff --git a/report/tests/data/report.md b/report/tests/data/report.md
new file mode 100644
index 0000000..6d37c8f
--- /dev/null
+++ b/report/tests/data/report.md
@@ -0,0 +1,36 @@
+# Sneakers
+
+## Air Max
+
+### Air Max 90
+
+One of the more icon color is the infrared.
+
+### Air Max 95
+
+Launched in 1995, designed by Sergio Lozano.
+
+### Air Max 97
+
+Well highlighted Air bubble.
+
+Inspired by mountain bikes, while an urban legend quotes Japan bullet trains as inspiration.
+
+## Other Nike Air
+
+### Introduction
+
+Because there are other sneakers than Air Max.
+
+### Air Force 1
+
+« Air Force 1. Zéro fan, que des fanatiques. » -- LTA
+
+## 👟
+
+---
+
+| Property | |
+|----------|---------------|
+| Date | 9999-99-99 |
+| Topic | Urban culture |
diff --git a/report/tests/data/report.xml b/report/tests/data/report.xml
new file mode 100644
index 0000000..f5cbe48
--- /dev/null
+++ b/report/tests/data/report.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<report title="Sneakers">
+ <section title="Air Max">
+ <entry title="Air Max 90">
+ <text>One of the more icon color is the infrared.</text>
+ </entry>
+ <entry title="Air Max 95">
+ <text>Launched in 1995, designed by Sergio Lozano.</text>
+ </entry>
+ <entry title="Air Max 97">
+ <text>Well highlighted Air bubble.
+
+Inspired by mountain bikes, while an urban legend quotes Japan bullet trains as inspiration.</text>
+ </entry>
+ </section>
+ <section title="Other Nike Air">
+ <entry title="Introduction">
+ <text>Because there are other sneakers than Air Max.</text>
+ </entry>
+ <entry title="Air Force 1">
+ <text>« Air Force 1. Zéro fan, que des fanatiques. » -- LTA</text>
+ </entry>
+ </section>
+ <section title="👟"/>
+ <data title="Properties">
+ <entry>
+ <key>Date</key>
+ <value>9999-99-99</value>
+ </entry>
+ <entry>
+ <key>Topic</key>
+ <value>Urban culture</value>
+ </entry>
+ </data>
+</report>

File Metadata

Mime Type
text/x-diff
Expires
Thu, Sep 18, 06:29 (12 h, 54 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2990129
Default Alt Text
(22 KB)

Event Timeline