Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F11722925
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
22 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Sep 18, 06:29 (21 h, 5 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2990129
Default Alt Text
(22 KB)
Attached To
Mode
rKERUALD Keruald libraries development repository
Attached
Detach File
Event Timeline
Log In to Comment